Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深度剖析0.1 +0.2===0.30000000000000004的原因 #7

Open
FrankKai opened this issue Mar 2, 2018 · 0 comments
Open

深度剖析0.1 +0.2===0.30000000000000004的原因 #7

FrankKai opened this issue Mar 2, 2018 · 0 comments
Labels

Comments

@FrankKai
Copy link
Owner

@FrankKai FrankKai commented Mar 2, 2018

用一句话概括就是:
EcmaScrpt规范定义Number的类型遵循了IEEE754-2008中的64位浮点数规则定义的小数后的有效位数至多为52位导致计算出现精度丢失问题!

如果你看不懂这句话,仔细阅读本篇博客就对了!

首先看下10进制转换为2进制的方法。

数字逻辑电路上的算法是 (0.1)10 = (0.0)2。

吐槽一句,大二的专业课数字逻辑电路终于用在工作上了。

0.1*2 = 0.2 ,整数位为0,且精度只到十分位,因此是0.0。

如果是不限精度的话,转换后的二进制数应该是:0.000110011001100110011(0011)无限循环。

如果表示成一个奇怪的形式,则是 (-1)^0*1.100110011(0011)* 2^-4

上述式子可类比十进制科学计数法公式。

0.0001234567 = 1.234567 * 10^-4 

为什么要这样表示?

-1的0次幂又是什么意思?

这是国际标准组织IEEE754对于浮点数表示方式的一种定义。

格式为;

(-1)^S x Mx 2^E

各符号的意思如下:
S,是符号位,决定正负,0时为正数,1时为负数。
M,是指有效位数,大于1小于2。 
E,是指数位。

因此才有了下面的形式:

(-1)^0*1.100110011(无限循环0011) * 2^-4
S = 0,M = 1.100110011(无限循环0011),E =-4

对应的0.2为:

(-1)^0*1.100110011(无限循环0011) * 2^-3
S = 0 ,M = 1.100110011(无限循环0011),E =-3

那么这和javascript有什么关系呢?

因为IEEE754标准里,还有两种特殊的定义。

IEEE 754规定,对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

问题还是一样,这和我们的javascript有什么关系呢?

因为javascript中Number类型,就是严格按照IEEE754标准来定义的。下面给出了最新版的ecma-262版本中关于Number类型的定义。

6.1.6
The Number Type
*
The Number type has exactly 18437736874454810627 (that is, ) values, representing the double-precision 64-bit format IEEE 754-2008 values as specified in the IEEE Standard for Binary Floating-Point Arithmetic, except that the 9007199254740990 (that is, ) distinct “Not-a-Number” values of the IEEE Standard are represented in ECMAScript as a single special  value.

再看一下wiki百科给出的IEEE754标准:

因此,javascript的Number类型, 最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

拿0.1举例来说:

(-1)^0*1.100110011(无限循环0011) * 2^-4

S = 0,M = 1.100110011(无限循环0011),E =-4

这里的无限循环就有限了,循环位数最多只能有52位.

JS中的0.1,在引擎中运算时,实质上会编译成:

1.1001100110011001100110011001100110011001100110011001*2^-4

0.2同理,会编译成:

1.1001100110011001100110011001100110011001100110011001*2^- 3

拿出关键的指数部分和有效位部分:


-4  0.1001100110011001100110011001100110011001100110011001 ①

-3  0.1001100110011001100110011001100110011001100110011001 ②·

①式转化为纯小数,小数最低位的1001被高位的0000挤出有效范围,得到③式

②式转化为纯小数,小数最低位的001被高位的000挤出有效范围,得到④式

原因是什么?

原因就是JS中的Number类型,二进制小数的有效位数只有52位,从0到51位(包括边界)。

在chrome控制台输入(0.1).toString('2')并打印结果为:"0.0001100110011001100110011001100110011001100110011001101"

不多不少,小数部分刚好52位,与规范以及我们的猜想完全契合。

回到0.1+0.2===0.30000000000000004这个经典问题。

在EcmaScript中,无关Browser环境,还是Nodejs环境,0.1+0.2的实际计算过程如下:


     0.0000100110011001100110011001100110011001100110011001 ③

    +0.0001001100110011001100110011001100110011001100110011 ④

---------------------------------------------------------------------------------------------------

    =0.0100110011001100110011001100110011001100110011001100 ⑤

最后得到的⑤式其实0.300000000000000004(17位十进制数)的二进制形式。

这就是0.1+0.2 ===0.300000000000000004的原因。

虽然我们期望的理想结果是返回0.3,恰恰印证了现实往往很骨感的说法。

有没有让0.1+02返回为0.3的办法呢?

因为不只是这一个精度丢失特例,还有很多情况都会造成精度丢失,比如:

0.3 / 0.1===2.9999999999999996以及0.7 * 180==125.99999999998等等。

那么有没有办法解决这个问题呢?

移步下一篇博客:如何解决0.1 +0.2===0.30000000000000004类问题

鸣谢单位:

https://segmentfault.com/a/1190000005022170

http://demon.tw/copy-paste/javascript-precision.html

http://www.ruanyifeng.com/blog/2010/06/ieee_floating-point_representation.html

http://www.css88.com/archives/7340#more-7340

http://www.ecma-international.org/ecma-262/8.0/index.html

https://en.wikipedia.org/wiki/Floating-point_arithmetic#Internal_representation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
1 participant
You can’t perform that action at this time.