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

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

0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

令人惊讶的是,他们是平等的: 奇怪的输出

为什么会发生? 他们是明显不同的数字(即使是0xFFFF...FFFF是一个数字更短)

如果我添加一个F0xFFFF...FF ,他们不再是相等的: 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 即使是陌生人的输出

这是预期的行为?

JavaScript中的所有数字在内部均由64位浮点数表示 (请参阅规范的第4.3.19节 )。 这意味着它可以精确地表示从09007199254740992 (hex值0x20000000000000 )中的每个整数。 任何大于(或小于它的负值)的整数可能需要四舍五入到最接近的近似值。

注意:

 9007199254740992 === 9007199254740993 > true 

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

 9007199254740992 === 9007199254740994 > false 

这就是你在第二个片段中看到的另一个F数字。

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

让我们来分析一下数字: 0xFFFFFFFFFFFFF为52位,内部表示为0x10000000000000。

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

当您在内部表示任何IEEE754双精度F​​P编号时 ,您会得到:

 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没有存储在归一化的数字中(每个非零的指数只有归一化的数字,mantissas for指数为000 0000 0000数字不遵循此规则)。 这意味着每个规格化的尾数有52个显式位,而隐式的1个

现在:52位呢? 注意0xFF …有52位长度。 这意味着它将被存储为:0表示符号(它是正数),52表示指数,52表示尾数(见本答案底部的最后一个音符)。 由于一个“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检测,相对而言,这些数字只有在这个ε不同,所以他们似乎只在这种情况下是相等的(有很多次这样做不是在0.3 == 0.2 + 0.1情况下保存,每个数字都是二进制不可表示的,相反,对于0.5情况,可以容忍0.1 + 0.4的错误)。

注意关于尾数和FP表示:在概念上,尾数始终低于1.如果要表示更高的数字,则必须使用指数构思它。 例子:

  • 0.5表示为0.5 * 2 ^ 0 (考虑math中正确的算子优先级)。
  • 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(考虑到我将在后面展示的尾数possition)。 最低的正数是:

 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总是告诉。

最低指数是(-1023)对应的000 0000 0000 。 有一条规则:每个尾数必须以(隐含的)“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)都使用了mantissas。 问号代表任何一点。

以下hex数字:

 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF 

存储为IEEE 754标准浮点值:

 1.3407807929942597e+154 

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

 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 

存储为:

 1.3407807929942597e+154 

这两个数字都超出了JavaScript编号( ref )可以准确表示的数字范围。 在上面的例子中,两个数字最后都有相同的内部表示,因此它们是相同的。

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

这显然是溢出或舍入。 在math上算出数量的大小,并检查最大数量。