快速理解 0.1 + 0.2 不等于 0.3

144 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

快问快答

64 位操作系统能表示的最大整数是多少?

64 位有符号整型能表示的最大值为 2 ^ 63 - 1 = 9223372036854775807,这是一个 19 位数。

MySQL 数据库的 id 字段经常用 19 位的长整型表示。

JS 能表示的最大数是多少?

JS 能表示的最大整数:Number.MAX_SAFE_INTEGER = 9007199254740991,约为 9e16

JS 能表示的最大正数:Number.MAX_VALUE = 1.7976931348623157e+308,约为 1.79e308

为什么 JS 的最大整数不是 19 位,而是 16 位?

JS 的正整数是用的尾数的长度表示,由于尾数是 52 位,加上整数的 1 位,它能表示的最大整数是 2 ^ 53 - 1 = 9007199254740991,是 16 位的。

为什么 JS 的最大正数是 1.79e308?

这个数是双精度浮点数所能表示的最大正值。

为什么 JS 有尾数的概念?

JS 内的数字运算采用 IEEE 754 标准,IEEE 二进制浮点数算术标准是20世纪80年代以来最广泛使用的浮点数运算标准。

存储形式:

  • Bit 63:最高有效位被指定为符号位
  • Bit 62-52:次高有效的11位是指数位
  • Bit 51-0:低有效的52位尾数存储有效数的小数部分

因为 JS 的整型和浮点型在计算过程中可以随时自动切换,整型在 JS 里也是用浮点数的解构存储的,放在尾数的部分,所以 JS 的数字运算方式实际是 IEEE 754 的运算方式。

为什么 0.1 + 0.2 != 0.3 ?

JS 内的浮点数计算在进制转换对阶运算中会导致结果偏差。

基础原理

浮点数相加,需要先转换成二进制,然后比较阶码是否一致,一致则尾数直接相加,不一致需要先对阶,小阶向大阶看齐(即把小阶的指数调成和大阶的一样大),并把它的尾数向右移相应位数,超出部分采用 “四舍五入” 形式截断进位。

进制转换(十进制 -> 二进制)

0.1 -> 0.0001100110011001... -> 1.10111001100... * 2 ^ -4

0.2 -> 0.0011001100110011... -> 1.10111001100... * 2 ^ -3

对阶运算

0.1 的小数点右移一位变成 1.10111001100... * 2 ^ -3

右移一位导致尾数需要截断,由于最后一位刚好是 0,这里直接舍弃。

再相加时,发生了进位,变成了 53 位,超过尾数 52 位的范围,所以阶码再进一位,即指数 + 1(乘以2),两数和的尾数右移一位,即除以2,由于尾数最后一位是1,四舍五入,舍弃最后一位后再加上 1,结果就变成了

1011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0100 * 2 ^ -2

进制转换(二进制 -> 十进制)

转换成 0.30000000000000004

结果

0.1 + 0.2 != 0.3

怎么判断 0.1 + 0.2 = 0.3?

ES6 新增了 Number.EPSILON 属性,使用 0.1 + 0.2 - 0.3 < Number.EPSILON 判断,Number.EPSILON 表示一个很小的小数,差值小于这个数则认为两者相等。