0.1 + 0.2
// => 0.30000000000000004
0.2 + 0.2
// => 0.4
0.3 + 0.2
// => 0.5
我理解它与浮点有关,但这究竟发生了什么?
根据@Eric Postpischil的评论,这不是重复:
那个只涉及为什么“噪音”出现在一个添加中。这个 问为什么“噪音”出现在一个加法中并且没有出现 另一个。在另一个问题中没有回答这个问题。因此,这 不是重复的。事实上,造成差异的原因尚未到期 到浮点运算本身,但是由于ECMAScript 2017 7.1.12.1第5步
答案 0 :(得分:3)
在JavaScript中将Number值转换为字符串时,默认值为to use just enough digits to uniquely distinguish the Number
value。 1 这意味着当数字显示为“0.1”时,这并不意味着它正好是0.1 ,只是它比任何其他数值更接近0.1,所以只显示“0.1”告诉你它是这个唯一的数值,即0.1000000000000000055511151231257827021181583404541015625。我们可以用十六进制浮点表示法写为0x1.999999999999ap-4。 (p-4
表示将前面的十六进制数乘以-4的幂,因此数学家会将其写为1.999999999999 16 •2 -4 。 )
以下是在源代码中编写0.1
,0.2
和0.3
时产生的值,并将它们转换为JavaScript的数字格式:
当我们评估0.1 + 0.2
时,我们正在添加0x1.999999999999ap-4和0x1.999999999999ap-3。要手动执行此操作,我们可以先将其有效数(小数部分)乘以2并从其指数中减去1来调整后者,从而生成0x3.3333333333334p-4。 (你必须用十六进制做这个算术。 16 •2 = 14 16 ,所以最后一个数字是4,1是携带的。然后9 16 •2 = 12 16 ,并且携带1使其13 16 。这产生3位数和1位数。)现在我们有0x1。 999999999999ap-4和0x3.3333333333334p-4,我们可以添加它们。这产生4.ccccccccccccep-4。这是确切的数学结果,但它对于数字格式有太多位。我们在有效数字中只能有53位。 4个(100 2 )中有3个比特,每个后13个数字中有4个比特,因此总共为55比特。计算机必须删除2位并舍入结果。最后一个数字E 16 是1110 2 ,所以10位必须去。这些位正好是前一位的1/2,所以它是向上或向下舍入之间的关系。断开关系的规则说是舍入所以最后一位是偶数,所以我们向上舍入使11位变为100.E 16 变为10 16 ,导致进入下一个数字。结果是4.cccccccccccd0p-4,等于0.3000000000000000444089209850062616169452667236328125。
现在我们可以看到为什么打印.1 + .2
显示“0.30000000000000004”而不是“0.3”。对于数字值0.299999999999999988897769753748434595763683319091796875,JavaScript显示“0.3”,因为该数字比任何其他数字更接近0.3。它在小数点后的17 th 位上与0.3相差大约1.1,而在17 th 位数时,我们的相加结果与0.3相差约4.4。 。所以:
0.3
生成0.299999999999999988897769753748434595763683319091796875,打印为“0.3”。0.1 + 0.2
生成0.3000000000000000444089209850062616169452667236328125,并打印为“0.30000000000000004”。现在考虑0.2 + 0.2
。结果是0.40000000000000002220446049250313080847263336181640625。这是最接近0.4的数字,因此JavaScript将其打印为“0.4”。
最后,考虑0.3 + 0.2
。我们正在添加0x1.999999999999ap-3和0x1.3333333333333p-2。我们再次调整第二个操作数,产生0x2.6666666666666p-3。然后添加产生0x4.0000000000000p-3,即0x1p-1,即½或0.5。因此打印为“0.5”。
另一种看待它的方式:
0.1
和0.2
的值分别略高于0.1和0.2,并且添加它们产生的数字大于0.3,错误得到加强,因此总误差很大足以将结果推离0.3,足以让JavaScript显示错误。0.2 + 0.2
时,错误会再次加剧。但是,在这种情况下,总错误不足以将结果推得远远超过0.4,JavaScript会以不同的方式显示它。0.3
的值略低于0.3。当添加到0.2
(略大于0.2)时,错误被取消,正好产生0.5。1 本规则来自ECMAScript 2017语言规范第7.1.12.1节中的第5步。
答案 1 :(得分:0)
这不是javascript本身或任何其他语言的问题,而是两个不同种族之间的沟通问题:如人和机器以及两者的thinking
能力。对我们来说看起来很自然(就像一个词:tree
- 当我们说我们在脑中创建一些树的抽象表示时)对于计算机而言是完全不自然的,并且机器可以做的唯一事情就是一个词"树"是以机器易于理解的某种代表方式存储它(你真正想要的任何方式,很多年前有人选择了带有ASCII表的二进制代码,它现在看起来很稳固)。所以此后机器会在那里存储一个单词tree
的表示,让我们说它是00000001
,但除此之外它并不知道任何事情,对你而言它有一些意义,对于一台机器来说,它只是一堆零和一个。如果我们然后说每个单词可能最多有7位,因为否则计算机工作缓慢,然后机器会保存0000000
切割最后一位,因此它仍然会以某种方式理解单词tree
。数字相同,0.3
对你来说很自然,但是当你看到10101010001010101010101010111
时,你会立即将其转换为十进制来理解它代表什么,因为它不自然你在二进制系统中看到数字。这里有一个重点:转换。
因此,你的数学看起来像这样:
.1 + .2 => .3
对于使用二进制系统的计算机,如下所示:
数字1/10可以用十进制表示为0.1,但是二进制表示为0.0001100110011001100110011001100110011001100110011001 ... ..因为根据标准,数字只有53位空间,从第54位开始,数字将被舍入。
x = .1 converted to 00011001...01 and trimmed to 53 bits
y = .2 converted to 00010110...10 and trimmed to 53 bits
z = x + y => 0001100010101... trimmed to 53 bits
result = 0.3000000000000000444089209850062616169452667236328125 converted from z = 0001100010101...
就像将欧元兑换成美元一样,有时你会获得半分钱,有时候你会以1美元的代价支付一半的欧元,因为没有比这更小的一块钱。可能会有,但人们会因为口袋而疯狂。
所以真正的问题是Why does 0.1 converted to binary and trimmed + 0.2 converted to binary and trimmed return unpredictable float results in JavaScript while 0.2 converted to binary and trimmed + 0.3 converted to binary and trimmed does not?
,答案是:因为数学和给定的计算能力,类似于pi + 1
给出奇怪结果的原因,但2 + 1
不= >你可能会像3.1415
那样推出一些pi代表,因为你没有足够的数学能力(或者它不值得)来制作一个确切的结果。
要了解更多信息,请在此处完成大量数学运算:https://medium.com/dailyjs/javascripts-number-type-8d59199db1b6