繁体   English   中英

Javascript浮点数混乱

[英]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编号

JavaScript编号在IEEE754中表示,它是双精度二进制浮点(binary64),它是科学计数法并以2为基础。 一个数字中有64位,它们被分成3个部分(从高位到低位):

  • 第一位用于符号:0 - 正; 1 - 否定
  • 接下来的11位是指数部分
  • 最后52位用于尾数/分数

在此输入图像描述

因此,浮点数的计算公式为:( - (-1) ^ sign * (2 ^ exponent) * significand

注意: 由于科学记数法的指数部分可以是正数或负数,因此二进制64数的实际指数值应通过从11位指数值中减去指数偏差(中间值1023)来计算。

该标准还将有效数值定义为[1, 2).之间[1, 2). 由于有效数字部分的第一个数字始终为1,因此它是暗示的,并未在上图中显示。 因此,实际上有效数部分实际上具有53位精度,而上图中的红色部分只是尾数或分数部分。

二进制64格式的0.1,0.2和0.3

根据标准,在二进制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

添加2个binary64数字的步骤

第1步 - 对齐指数

  • 移位指数较小的数字的有效数
  • 将有效数字右移
  • 对于每个有效位移,将指数增加1,直到两个指数相同
  • 转移后,有效数字应该向上舍入。

第2步 - 添加有效数字

  • 如果累加的有效数字不满足[1,2)要求,则将其移至该范围并更改指数

  • 转移后,有效数字应该向上舍入。

0.1 + 0.2 + 0.3 == 0.6000000000000001

如上所述, 0.1具有指数-40.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的问题,你也会在其他语言中得到类似的惊喜。

请阅读: 每个程序员应该知道的浮点运算

一般问题在Is浮点数学中被描述了吗?

在其余部分中,我将只看两个计算之间的区别。

从我的评论:

那么,在第一种情况下你做(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.

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