【问题标题】:SafeMath Class. How to create chainable calculations?安全数学课。如何创建可链接的计算?
【发布时间】:2021-08-04 07:59:37
【问题描述】:

我很难找到要在线搜索的关键字。

我创建了一个具有安全数学函数的类。每个函数接受 2 个参数,并在通过断言评估后返回结果。

例子:

class SafeMath {

  static add(x: number, y: number) {
    let z: number = x + y;
    assert(z >= x, 'ds-math-add-overflow');
    return z;
  }

  static sub(x: number, y: number) {
    let z: number = x - y;
    assert(z <= x, 'ds-math-sub-underflow');
    return z;
  }

  static mul(x: number, y: number) {
    let z: number = x * y;
    assert(y == 0 || z / y == x, 'ds-math-mul-overflow');
    return z;
  }

  static div(x: number, y: number) {
    let z: number = x / y;
    assert(x > 0 || y > 0, 'ds-math-div-by-zero');
    return z;
  }

}

console.log(SafeMath.add(2,2)); // 4
console.log(SafeMath.sub(2,2)); // 0
console.log(SafeMath.mul(2,2)); // 4
console.log(SafeMath.div(2,2)); // 1

我的目标是让这些函数像这样工作,例如:

let balance0: number = 1;
let balance1: number = 1;

let amount0In: number = 10;
let amount1In: number = 10;

let balance0Adjusted: number = balance0.mul(1000).sub(amount0In.mul(3));
let balance1Adjusted: number = balance1.mul(1000).sub(amount1In.mul(3));

...函数将接收y 并将之前的数字用作x

【问题讨论】:

  • 1 是一个数字,它没有 mul 方法。如果以let balance0: SafeNumber = new SafeNumber(1); 开头,则可以在SafeNumber 类中定义自己的方法。
  • 顺便说一句,您是否要表示无符号整数? JS 对所有内容都使用浮点数,不会溢出。请注意您的 addsub 方法不适用于负整数。
  • 对无符号整数更正。更正 addsub 故意不使用负整数。
  • 好的,但是x + y 不是无符号整数的加法。

标签: javascript typescript deno


【解决方案1】:

你可以为此做一些包装:

if (!Number.prototype.mul)  // check that the mul method does not already exist 
  {
  Number.prototype.mul = function(n){ return this * n }
  }
  
if (!Number.prototype.add)
  {
  Number.prototype.add = function(n){ return this + n }
  }
  
  
let val = 5
let doubleValPlus500 = val.mul(2).add(500)

console.log( doubleValPlus500 )

【讨论】:

  • 错误:TS2339 [错误]:“数字”类型上不存在属性“mul”。
  • @suchislife 如果它在“简单”JS 中工作,我想 typeScript 中的语法是不同的......
  • 我正在创建一个基于@Bergi 的打字稿版本。让我们看看我们得到了什么。
  • @suchislife 我不知道打字稿,我还在寻找打字稿包装器的例子,确实有,但是打字稿的句法规则完全让我忘记了!
  • @MisterMojo,看看 deno.land
【解决方案2】:

您可以修改Number.prototype 以添加功能,以便您可以链接这些操作。使用string 属性键这样做通常被认为是一种不好的做法(请参阅Why is extending native objects a bad practice?)。您可以使用唯一的symbol 属性键而不是字符串属性键来避免名称冲突等。

这是一个示例模块,它使用唯一符号安全地“扩展”Number.prototype,并使用唯一符号将新函数签名添加到 TypeScript Number 接口:

mul.ts

const mul = Symbol("multiply");

function value(this: number, n: number) {
  return this * n;
}

declare global {
  interface Number {
    [mul]: typeof value;
  }
}

Object.defineProperty(Number.prototype, mul, { value });

export default mul;

在定义了一个像上面这样的模块后,你可以导入模块并使用它们导出的唯一符号来链接操作:

import mul from "./mul.ts";
import sub from "./sub.ts";

const balance = 1;
const amountIn = 10;
const balanceAdjusted = balance[mul](1000)[sub](amountIn[mul](3));
console.log(balanceAdjusted);
970

使这些数学运算可链接的一个好处是,当您处理有时会派上用场的空值时,您可以将它们与optional chaining operator 结合起来。


同样可以在不使用符号的情况下完成,但对于可能为 mul 等定义自己的 Number 方法的 JavaScript 的未来版本并不安全:

mul.ts

function value(this: number, n: number) {
  return this * n;
}

declare global {
  interface Number {
    mul: typeof value;
  }
}

Object.defineProperty(Number.prototype, "mul", { value });

export {}; // you have to import or export something to make it a module
import "./mul.ts";
import "./sub.ts";

const balance = 1;
const amountIn = 10;
const balanceAdjusted = balance.mul(1000).sub(amountIn.mul(3));
console.log(balanceAdjusted);
970

单独导入所有这些模块可能不太方便,因此您也可以制作一个模块来组合所有其他模块:

ma​​th.ts

export { default as mul } from "./mul.ts";
export { default as sub } from "./sub.ts";
/* and so forth */

然后你可以导入它并选择你想要使用的:

import { mul, sub } from "./math.ts";

const balance = 1;
const amountIn = 10;
const balanceAdjusted = balance[mul](1000)[sub](amountIn[mul](3));
console.log(balanceAdjusted);

【讨论】:

  • 非常方便。有时非常。我不认为用点来做是可能的吗?
  • 这是通过使用mul 而不是[mul] 但是如果Number.prototype 稍后定义了自己的mul 方法,如果它的行为不同等可能会导致问题。
  • 我更新了我的答案,添加了不安全的示例代码。
  • 甜蜜。最后,我试图找到一种方法,让一个导入包含所有功能。我创建/想要使用的每一个数学函数都不是一个。当我尝试将函数名称从 value 更改为 multiply 时,我得到了错误。请记住,我知道Number.prototype.add =...,但喜欢你的安全方法。
  • 就我个人而言,我会将它们全部定义在单独的模块/文件中,然后使用多个重新导出语句(如export { default as mul } from "./mul.ts";)将它们组合在一起,然后继续;这样您就可以从一个模块中导入所有这些并使用您想要的那些
【解决方案3】:

基于 Number.prototype 的示例

import { assert } from "https://deno.land/std@0.102.0/testing/asserts.ts";

declare global {

  /*
    Augument global Number.prototype with the following custom functions

    Warning - While this may look like a clean approach, it is considered 
    unsafe due to javascript possibly choosing to natively implement these
    exact function names in the near future. To avoid this, choose unique
    function names.

  */
  interface Number {
    add(n: number): number;
    sub(n: number): number;
    mul(n: number): number;
    div(n: number): number;
    pow(n: number): number;
    sqrt(): number;
    print(): number;
  }
}

Number.prototype.add = function(this:number, n:number) {

  assert((this + n) >= this, 'ds-math-add-overflow');
  return this + n;
}

Number.prototype.sub = function(this:number, n:number) {

  assert((this - n) <= this, 'ds-math-sub-underflow');
  return this - n;
}

Number.prototype.mul = function(this:number, n:number) {

  assert(n == 0 || (this * n) / n == this, 'ds-math-mul-overflow');
  return this * n;
}

Number.prototype.div = function(this:number, n:number) {

  assert(this > 0 || n > 0, 'ds-math-div-by-zero');
  return this / n;
}

Number.prototype.pow = function(this:number, n:number) {

  assert(this > 0 && n >= 2, 'ds-math-exp-to-zero');
  return this ** n;
}

// babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
Number.prototype.sqrt = function(this:number) {

  assert(this > 0, 'ds-math-sqrt-of-zero');

  let x: number = 0;
  let y: number = this;
  let z: number = 0;

  if (y > 3) {
    z = y;
    x = y / 2 + 1;
    while (x < z) {
      z = x;
      x = (y / x + x) / 2;
    }
  } else if (y != 0) {
    z = 1;
  }
  return z;
}

Number.prototype.print = function(this:number) {

  console.log('=', this);
  return this;
}

// Here it is in action:

let balance = 0;

balance.add(10).print().sub(1).print().mul(2).print().div(3).print().pow(4).print().sqrt().print();

输出:

= 10
= 9
= 18
= 6
= 1296
= 36

基于类的示例

import { assert } from "https://deno.land/std@0.102.0/testing/asserts.ts";

class SafeMath {
  private n: number;

  constructor(start: number = 0) {
    this.n = start;
  }

  public add(y: number) {

    assert(this.n + y >= this.n, 'ds-math-add-overflow');

    let z: number = this.n + y;

    this.n = this.n + y;
    return this;
  }

  public sub(y: number) {

    assert(this.n - y <= this.n, 'ds-math-sub-underflow');

    let z: number = this.n - y;

    this.n = this.n - y;
    return this;
  }

  public mul(y: number) {

    assert(y == 0 || (this.n * y) / y == this.n, 'ds-math-mul-overflow');

    let z: number = this.n * y;

    this.n = this.n * y;
    return this;
  }

  public div(y: number) {

    assert(this.n > 0 || y > 0, 'ds-math-div-by-zero');

    let z: number = this.n / y;

    this.n = this.n / y;
    return this;
  }

  public pow(y: number) {

    assert(this.n > 0 && y >= 2, 'ds-math-exp-to-zero');

    let z: number = this.n ** y;
    this.n = this.n ** y;
    return this;
  }

  // babylonian method (https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Babylonian_method)
  public sqrt() {

    assert(this.n > 0, 'ds-math-sqrt-of-zero');

    let x: number = 0;
    let y: number = this.n;
    let z: number = 0;

    if (y > 3) {
      z = y;
      this.n = z;
      x = y / 2 + 1;
      while (x < z) {
        z = x;
        this.n = z
        x = (y / x + x) / 2;
      }
    } else if (y != 0) {
      z = 1;
      this.n = z
    }
    return this;
  }

  public print() {
    console.log('=', this.n);
    return this;
  }

}

// Here it is in action:

new SafeMath(0).add(10).print().sub(1).print().mul(2).print().div(3).print().pow(4).print().sqrt().print();

输出:

= 10
= 9
= 18
= 6
= 1296
= 36

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多