likes
comments
collection
share

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

作者站长头像
站长
· 阅读数 21

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

为什么 0.1 + 0.2 !== 0.3

console.log( 0.1 + 0.2 === 0.3) //false

在做算术运算时,JS 会先把十进制数转换成二进制数后再计算,十进制小数转二进制数的方式是 x 2 取整,0.1 和 0.2 的二进制数是个无限循环小数。

而 JS 中表示一个数字只有 64 位,其中精度位(有效数位)只有 52 位,所以当出现无限循环小数时,会通过 0 舍 1 入 的规则截取前 52 位(类似十进制的四舍五入),这样导致了精度位的丢失。0.1 实际参与计算的数变大了,0.2 参与计算的数变小了,所以运算结果不一定等于 0.3。

0.1 + 0.2 多出的 0.0...4 是从哪里来的

console.log( 0.1 + 0.2) //0.30000000000000004

0.1 转二进制计算

0.1 的二进制数是无限循环小数: 0.00011001100110011001100110011001100110011001100110011001...

第 53 位是 1,在截取前 52 位时 0 舍 1 入后实际参与运算的是: 0.0001100110011001100110011001100110011001100110011010

0.2 转二进制计算

0.2 的二进制数是无限循环小数: 0.00110011001100110011001100110011001100110011001100110011...

第 53 位是 0,在截取前 52 位时 0 舍 1 入后实际参与运算的是: 0.0011001100110011001100110011001100110011001100110011

多出的 0.0...4

所以 0.0...4 是在转二进制截取时 0 舍 1 入 0.1 入的部分和 0.2 舍去部分之和。

为什么 0.1 + 0.1 === 0.2

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

0.1 转二进制后是个近似值,0.2 转二进制后也是个近似值,两个近似值相加后的二进制数碰巧等于另一个近似值。

这里只是碰巧相等,浮点数的比较方式还是不能通过 === 号比较。

正确的浮点数比较方式

正确的浮点数比较方式是比较绝对值是否在 JS 提供的最小精度范围内

console.log( Math.abs(0.1 + 0.2 - 0.3) <= Number.EPSILON) //true

JavaScript 如何表示一个数字

双精度浮点数

JS 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,IEEE 可以读作 I treble E。根据浮点数的定义,双精度浮点数由 64 位组成,如下图:(这里的位是指二进制,0 或 1表示)

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

64 位分别为:

  • 符号位 1 位:不参与计算,0 表示正数 1 表示负数;
  • 指数位 11 位:有个基准值 01111111111,小于这个数时指数位就是负数。因为指数位没有符号位,而科学计数法的指数位可以为负数。转换的时候要减去这个基准值;
  • 有效位数 52 位:有效数字隐藏了个 1,因为以 0 开头没有意义;

有效位数决定了这个数的精度,指数位表示浮点数表示的范围。浮点数可以表示很大的数,在表示最大数的时候,可能不是每个整数都能表示了,数越大能表示的数就越稀疏,因为有效位数精度会丢失。64 位二进制位转十进制公式为:

  • 十进制数字 = 符号位 + 有效数位 x 2^(指数-1023)次方

上文中的指数位、有效位数对应的是科学计数法。十进制数转换成二进制后,再转换成二进制的科学计数法数字,从而得到指数位、有效数位。

10 进制数转 64 位浮点数的过程

10进制转二进制
二进制转科学计数法得到指数位和有效数位
指数位和有效数位通过二进制表示

以十进制数 12.25 为例,转换成 64 位二进制过程如下:

  • 第一步,将整数部分 12 转换成二进制得到 1100;
  • 第二步,将小数部分 0.25 转换成二进制得到 0.01;
  • 第三步,合并结果为 1100.01;
  • 第四步,将二进制数转换成科学计数法,得到指数位和有效数位
    • 根据科学计数法规则,整数部分保留 1 位,所以小数点往左移动 3 位,得到指数位 3 和有效数位 1.10001
  • 第五步,确定双精度浮点数每一位的值
    • 符号位 1 位:0 正数
    • 指数位 11 位:3 -> 00000000011 -> 00000000011 + 01111111111(基准值)-> 10000000010
    • 有效数位 52 位:1.10001 -> 1.1000100000000000000000000000000000000000000000000000 -> 1000100000000000000000000000000000000000000000000000,第一位是 1 隐藏了。
  • 第六步,合并每位结果为:0100000000101000100000000000000000000000000000000000000000000000

进制转换

十进制小数如何转二进制

转换规则为 x 2 取整,十进制数 0.8125 = 0.11101,转二进制过程如下:

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

十进制数 0.2 = 0.0011001100110011001100110011001100110011001100110011 ...,从小数点第 5 位开始进入无限循环,转二进制过程如下:

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

十进制整数数如何转二进制浮点数

转换规则为 ÷ 2 取余,十进制数 125 = 1111101,转二进制过程如下:

[路飞]_面试题_彻底搞懂为什么 JavaScript 0.1 + 0.2 !== 0.3

推荐转换工具:

如有错误欢迎指出,欢迎一起讨论!