繁体   English   中英

为什么 JavaScript 中两个不同的数字相等?

[英]Why are two different numbers equal in JavaScript?

我一直在搞一个 JavaScript 控制台,当我突然决定尝试这个时:

0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

令人惊讶的是,它们是相等的:奇怪的输出

为什么会发生? 它们显然是不同的数字(即使是0xFFFF...FFFF也短一位数)

如果我添加一个F0xFFFF...FF ,他们不是平等了: 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF更陌生的输出

这是预期的行为吗?

JavaScript 中的所有数字都在内部由64 位浮点数表示(参见规范的§4.3.19 )。 这意味着它可以准确地表示从09007199254740992 (十六进制值0x20000000000000 )的每个整数。 任何大于(或小于其负对应值)的整数可能需要四舍五入到最接近的近似值。

观察:

9007199254740992 === 9007199254740993
> true

但是,四舍五入为足够不同的近似值的两个数字在比较它们时仍会评估为不同的值。 例如:

9007199254740992 === 9007199254740994
> false

这就是您在添加另一个F数字的第二个代码段中看到的内容。


注意: ECMAScript 规范现在将Number.MAX_SAFE_INTEGER定义为等于9007199254740991的全局常量。

0x100000000000000 == 0xFFFFFFFFFFFFFF给出true0x10000000000000 == 0xFFFFFFFFFFFFF给出false 所以前者是“极限”,比如说。

让我们分析数字:在内部表示中,52 位表示0xFFFFFFFFFFFFF ,另外 1 位表示 0x10000000000000。

编辑:这种量级的数字不是由长整数表示,而是由双精度浮点数表示。 这是因为它们超过了整数值的 32 位表示, javascript 中的每个数字都表示为 IEEE754 双精度浮点数。

当您在内部表示任何IEEE754 双精度 FP 数时,您将得到:

0111 1111 1111 2222
2222 2222 2222 2222
2222 2222 2222 2222
2222 2222 2222 2222

其中 (0) 是符号位,(1) 指数位,以及 (2) 尾数位。

如果您在 JavaScript 0.5 == 5 * 0.1进行比较,即使该操作具有浮动不精确性(即您得到一些错误),您也会得到true 所以 Javascript 可以容忍浮点运算中的小错误,所以像常识一样,这样的运算是正确的。

编辑- 我写的尾数有问题:是的,每个尾数都以1开头(据说这样的尾数是标准化的),但是1没有存储在标准化数中(每个非零指数只有标准化数。尾数为指数为000 0000 0000数字不遵循此规则)。 这意味着每个标准化尾数都有 52 个显式位和一个隐式 1

现在:那 52 位呢? 请注意, 0xFF... 有 52 位长度。 这意味着它将被存储为:0 表示符号(它是正数),52 表示指数,以及尾数中的 52 个“1”数字(请参阅本答案底部的最后一个注释)。 由于一个“1”是隐式的,我们将存储 51 个“1”和一个“0”。

0100 0011 0010 1111
1111 1111 1111 1111
1111 1111 1111 1111
1111 1111 1111 1110

(exponent 1075 corresponds to actual exponent 52)

另一个数字有 53 位:一个“1”和 52 个“0”。 由于第一个“1”是隐式的,它将被存储为:

0100 0011 0100 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

(exponent 1076 corresponds to actual exponent 53)

现在是比较值的时候了。 他们将在条件相等的情况下进行比较:首先我们将符号和指数用于比较。 如果它们相等,我们考虑尾数。

考虑到作为舍入乘积的小误差,这里进行比较。 这样的epsilon被考虑在内(epsilon 约为 =2^-53)并且 FP ALU 检测到,相对而言,这些数字仅在此类 epsilon 中不同,因此它们似乎仅在该上下文中相等(有很多次这样的情况)在0.3 == 0.2 + 0.1情况下,不会保存您,因为三个数字中的每一个都是二进制不可表示的,而0.5是,并且可以容忍针对0.1 + 0.4的错误)。

关于尾数和 FP 表示的注意事项:尾数在概念上始终小于 1。如果您想表示更大的数字,则必须使用指数来构思它。 例子:

  • 0.5 表示为0.5 * 2 ^ 0 (考虑数学中正确的运算符优先级)。
  • 1 不表示为1 * 2 ^ 0因为尾数总是小于 1,所以表示为0.5 * 2 ^ 1
  • 65 的二进制表示为 1000001,将存储为(65/128) * 2 ^ 7

这些数字表示为(请记住:第一个“1”是隐式的,因为这些指数用于归一化数字):

0011 1111 1111 0000
... more 0 digits

(exponent 1023 stands for actual exponent 0, mantissa in binary repr. is 0.1, and the first "1" is implicit).

0100 0000 0000 0000
... more 0 digits

(exponent 1024 stands for actual exponent 1, mantissa in binary repr. is 0.1, and the first "1" is implicit).

0100 0000 0110 0000
0100 0000 0000 0000

(exponent 1030 stands for actual exponent 7, mantissa in binary repr. is 0.1000001, and since the first "1" is implicit, it is stored as 0000 0100 0000...)

关于指数的注意事项:也可以通过允许负指数来实现较低的精度:指数看起来是正数 - 无符号 - 但实际情况是您必须将该数字减去 1023(称为“偏差”)才能获得实际指数(这意味着指数“1”实际上对应于 2^(-1022))。 将其转换为基于 10 的幂,十进制数的最低指数为 -308(还要考虑尾数位置,我将在后面展示)。 最小的正数是:

0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0001

即: (1 * 2^-52) * 2^-1023是尾数给出的第一个 -52 和指数给出的 -1023。 最后一个是:1 * 2^(-1075),它趋向于总是被告知的 10^-308。

最低指数是000 0000 0000对应于 (-1023)。 有一条规则:每个尾数都必须以(隐含的)“1”开头或具有该指数。 另一方面,最高指数可能是111 1111 1111 ,但这个指数是为特殊伪数保留的:

0111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

对应于 +Infinity,而:

1111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0000

对应于 -Infinity,以及具有非零尾数的任何模式,例如:

?111 1111 1111 0000
0000 0000 0000 0000
0000 0000 0000 0000
0000 0000 0000 0001

对应于 NaN(不是数字;非常适合表示 log(-1) 或 0/0 之类的东西)。 实际上,我不确定 NaN 使用什么尾数(安静或信号 NaN)。 问号代表任何位。

以下十六进制数:

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

存储为 IEEE 754 标准浮点值:

1.3407807929942597e+154

你在这个数字上加 1,它变成:

0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

存储为:

1.3407807929942597e+154

这两个数字都位于 JavaScript 数字 ( ref ) 可以准确表示的数字范围之外。 在上面的例子中,两个数字最终具有相同的内部表示,因此它们在某种程度上是相等的。

提醒:不应使用相等运算符 ( ref ) 比较浮点数。

这显然是溢出或舍入。 用数学方法计算出数字的大小并检查最大的数字。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM