在Javascript中比较大数

8

我有两个数字需要进行比较。下面这个例子中的数字是在两个不同系统中计算 26^26 的结果,其中之一是我的javascript代码。

然而,当我比较这两个数字时,得到的结果如下:

AssertionError [ERR_ASSERTION]: 4.0329146112660565e+26 == 4.0329146112661e+26

显然它们不相等,但从理论上讲它们应该相等。

在JavaScript中,对于大数进行相等比较,应该使用何种正确方法(即使这只是一个近似值)?


1
https://github.com/MikeMcl/bignumber.js/ - Mojtaba Kamyabi
你可以将数字转换为字符串,然后进行比较。 - guest271314
1
“相等”是什么意思?严格相等?还是在某些数字范围内相等?或者是在固定精度范围内相等?或者是可以互相四舍五入的数字?请具体说明。 - Vasily Liaskovsky
3
记住:在JavaScript中,每个“Number”都是一个64位浮点数,所以“Number.MAX_SAFE_INTEGER”常量代表JavaScript中最大的安全整数(2*53 - 1)。编辑*:所以如果要检查两个浮点数的“相等性”,则只有在以相同方式计算和四舍五入它们时才能进行检查。当您具有真正的整数时,您需要牢记的口诀是:只存储了x个LSB,而使用浮点数时,您需要牢记的口诀是仅存储了前53个MSB(其余被丢弃)。 - GitaarLAB
1
@GitaarLAB 很棒的回答! - Waseem
显示剩余3条评论
4个回答

4

更新: 如果你的目标引擎是 es2020 或更高版本, 你可以使用新的 BigInt JavaScript 基元,用于表示大于 Number.MAX_SAFE_INTEGER 的数字。

BigInt(4.0329146112660565e+26) === BigInt(4.0329146112661e+26)
//false

在MDN中查看更多信息


3
如果您想确定两个数字是否相等,您需要设定误差范围。一种方法是计算这两个数字之间的差异,然后确定这种差异是否显著。所以,我们可以通过减法计算前面提到的这两个数字之间的差异。由于我们不关心这种差异的正负,我们将取这种差异的绝对值。
Math.abs(4.0329146112660565e+26 - 4.0329146112661e+26) === 4329327034368

(顺便说一下:现在不是解释原因的时候,但是JavaScript中的==操作符具有混乱和容易出错的行为,当您需要比较值时,请使用===。)
这个差异是一个庞大的数字,但与我们最初的数字有多大相关,它相对无关紧要。直觉上,我倾向于将差异除以我们最初数字中最小的数字,如下所示:
4329327034368 / 4.0329146112660565e+26 === 1.0734983136696987e-14

看起来这是一个相当小的数字。如果您使用一堆值重复执行同样的操作,您应该能够确定您想要的误差范围。然后,您只需要对任意数字执行相同的操作,看看是否 "差异比率" 对您来说足够小。

function similar(a, b) {
  let diff = Math.abs(a - b);
  let smallest = Math.min(Math.abs(a), Math.abs(b));
  let ratio = diff / smallest;
  return ratio < MARGIN_OF_ERROR;
}

我刚刚想出了一种确定两个数字之间差异重要性的方法。这可能不是一个非常聪明的计算方法,它可能适用于某些情况而不适用于其他情况。但是总体思路是你需要编写一个函数来确定两个值是否足够接近,并使用自己定义的“接近”概念。

请注意,JavaScript 是你进行数学计算时最糟糕的语言之一。当整数超过 Number.MAX_SAFE_INT (似乎根据 Chrome 的说法为9007199254740991,不确定在其他浏览器中是否有所不同或是否为标准化常量) 时,它们会变得不精确。


1
标准化常量!IEEE 754浮点数的自然结果:记住口诀,只存储前53位(以及指数)。编辑:我们不包括最后一种可能的值(但是减去一个)的原因是因为我们想要安全的整数,并且我们不能区分2 ** 53 +1和2 ** 53。 - GitaarLAB
这实际上是针对相对精度而言,而不是固定精度,但是没错,它肯定可以帮助。 - Vasily Liaskovsky
1
旁注:JavaScript数学没有问题(我经常使用它)。只需了解有一个数字类型:IEEE 747 64位浮点数。你仍然得到我的+1!编辑:即使是比这更大的数字,只要您想要表示的值不需要超过53个有效位,您甚至可以使用可靠的数字进行计算!一旦理解了这一点,您就可以使用本机数字实现相当有趣的算法(通常是二进制)。 - GitaarLAB

1
var a = 4.0329146112660565e+26;

var b = 4.0329146112661e+26;

a = Math.round(a/10e+20)*10e+20

b = Math.round(b/10e+20)*10e+20

a == b;

@Daniel Lord 谢谢!10e+20似乎是一个有点神奇的数字。你介意解释一下它背后的逻辑吗? 这个解决方案与 @GitaarLAB 关于 Number.MAX_SAFE_INTEGER 的建议相结合,对我很有用。Math.round(4.0329146112661e+26 / Number.MAX_SAFE_INTEGER) * Number.MAX_SAFE_INTEGER; - Waseem
@Sam D. 看起来你想要精度小于12,因为这是浮点数不同的地方。这个解决方案将四舍五入到26-20+1=7的精度。在7的精度下,浮点逻辑可以正常工作。更改20以获得或失去比较的精度。 - Daniel Lord
为什么要将 2026 转换为数字,它们已经是数字了:10e+20 == 10e20 - dandavis

0

我建议使用一个大数库:

  1. big.js (https://www.npmjs.com/package/big.js)

例子:

var x = new Big('4.0329146112660565e+26');

var y = new Big('4.0329146112661e+26');

// Should print false
console.log('Comparision result' + x.eq(y));
  1. 大数(https://www.npmjs.com/package/big-numbers

示例:

var x = bn.of('4.0329146112660565e+26');
var y = bn.of('4.0329146112661e+26');

// Should print false
console.log('Comparision result' + x.equals(y));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接