[英]Javascript floating point number confusion
我遇到了一些混乱的操作。
var a = 0.1;
var b = 0.2;
var c = 0.3;
console.log(a); // 0.1
console.log(b); // 0.2
console.log(c); // 0.3
但,
consolo.log(a+b+c) // 0.6000000000000001.
而
console.log(a+(b+c)) // 0.6
据我所知,Javascript使用二进制浮点,因而无法准确表示0.1,0.2,0.3但是括号周围是什么(b + c)。 这里有转换或回合吗?
非常感谢,
JavaScript编号在IEEE754中表示,它是双精度二进制浮点(binary64),它是科学计数法并以2为基础。 一个数字中有64位,它们被分成3个部分(从高位到低位):
因此,浮点数的计算公式为:( - (-1) ^ sign * (2 ^ exponent) * significand
注意: 由于科学记数法的指数部分可以是正数或负数,因此二进制64数的实际指数值应通过从11位指数值中减去指数偏差(中间值1023)来计算。
该标准还将有效数值定义为[1, 2).
之间[1, 2).
由于有效数字部分的第一个数字始终为1,因此它是暗示的,并未在上图中显示。 因此,实际上有效数部分实际上具有53位精度,而上图中的红色部分只是尾数或分数部分。
根据标准,在二进制64格式中找到0.1,0.2和0.3并不困难(您可以手动或通过此工具http://bartaz.github.io/ieee754-visualization/计算 ):
0.1
0 01111111011 1001100110011001100110011001100110011001100110011010
在科学记数法中,它是
1.1001100110011001100110011001100110011001100110011010 * 2e-4
注意:有效位部分采用二进制格式,以下数字采用相同格式
0.2
0 01111111100 1001100110011001100110011001100110011001100110011010
在科学记数法中,它是
1.1001100110011001100110011001100110011001100110011010 * 2e-3
0.3
0 01111111101 0011001100110011001100110011001100110011001100110011
在科学记数法中,它是
1.0011001100110011001100110011001100110011001100110011 * 2e-2
第1步 - 对齐指数
第2步 - 添加有效数字
如果累加的有效数字不满足[1,2)
要求,则将其移至该范围并更改指数
转移后,有效数字应该向上舍入。
如上所述, 0.1
具有指数-4
和0.2
具有指数-3
,所以需要第一做指数对准:
从0.1
移位
1.1001100110011001100110011001100110011001100110011010 * 2e-4
至
0.1100110011001100110011001100110011001100110011001101 * 2e-3
然后添加有效数字
0.1100110011001100110011001100110011001100110011001101
同
1.1001100110011001100110011001100110011001100110011010
我们得到了有意义的价值:
10.0110011001100110011001100110011001100110011001100111
但它不在[1,2)
范围内,因此需要将其(向上舍入)右移:
1.0011001100110011001100110011001100110011001100110100 (* 2e-2)
然后将其添加到
0.3 (1.0011001100110011001100110011001100110011001100110011 * 2e-2)
我们得到:
10.0110011001100110011001100110011001100110011001100111 * 2e-2
同样,我们需要转移并向上舍入它,最后获得价值:
1.0011001100110011001100110011001100110011001100110100 * 2e-1
它正好是0.6000000000000001
(十进制)的值
使用相同的工作流程,您可以得到0.1 +(0.2 + 0.3)
此网页http://bartaz.github.io/ieee754-visualization/可帮助您快速将十进制数转换为二进制64格式,您可以使用它来验证计算步骤。
如果您正在处理单精度二进制浮点数,则可以参考此工具: http : //www.h-schmidt.net/FloatConverter/IEEE754.html
这不是JavaScript的问题,你也会在其他语言中得到类似的惊喜。
请阅读: 每个程序员应该知道的浮点运算
在其余部分中,我将只看两个计算之间的区别。
从我的评论:
那么,在第一种情况下你做(0.1 + 0.2)+ 0.3 = 0.3 + 0.3而在第二种情况下你做0.1 +(0.2 + 0.3)= 0.1 + 0.5。 我猜第一种情况下的舍入误差大于第二种情况。
让我们仔细看看这个计算中的实际值:
var a = 0.1; var b = 0.2; var c = 0.3; console.log(' a:', a.toPrecision(21)); console.log(' b:', b.toPrecision(21)); console.log(' c:', c.toPrecision(21)); console.log(' a + b:', (a + b).toPrecision(21)); console.log(' b + c:', (b + c).toPrecision(21)); console.log(' a + b + c:', (a + b + c).toPrecision(21)); console.log('a + (b + c):', (a + (b + c)).toPrecision(21));
输出是
a: 0.100000000000000005551
b: 0.200000000000000011102
c: 0.299999999999999988898
a + b: 0.300000000000000044409
b + c: 0.500000000000000000000
a + b + c: 0.600000000000000088818
a + (b + c): 0.599999999999999977796
因此,很明显两个计算都有舍入错误,但错误是不同的,因为您以不同的顺序执行添加。 恰好a + b + c
会产生更大的误差。
控制台似乎将数字四舍五入到第16个小数:
> (a + b + c).toPrecision(16)
"0.6000000000000001"
> (a + (b + c)).toPrecision(16)
"0.6000000000000000"
这就是为什么第二次计算只输出0.6
。 如果控制台将舍入到第17个小数,那么事情看起来会有所不同:
> (a + b + c).toPrecision(17)
"0.60000000000000009"
> (a + (b + c)).toPrecision(17)
"0.59999999999999998"
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.