Blog icon indicating copy to clipboard operation
Blog copied to clipboard

变量和类型 - 数字精度丢失(0.1 + 0.2 = 0.30000000000000004)

Open logan70 opened this issue 5 years ago • 0 comments

数字精度丢失(0.1 + 0.2 = 0.30000000000000004)

IEEE754

JavaScript中的Number类型是基于 IEEE 754 标准的双精度 64 位二进制格式的值。

  • 符号位:1位,标识数值正负,0为正,1为负
  • 指数部分:11位,表示范围为0~2047,减去偏移常数bias为1023,即实际范围为-1023~1024
    • 展开指数部分详细讲解

      指数偏移常量:计算指数时要减去的常量。 指数位数若为e,指数偏移常量bias则为 2e-1,双精度64位浮点数中,指数位数为11位,故偏移常量为1023,指数最终取值为 0 - 1023 ~ 2047 - 1023,即-1023~1024

      特殊指数:指数全0或全1有特殊含义,不算正常指数。

      • 指数全0,尾数全0,表示0。根据符号位不同可以分为+0-0
      • 指数全0,尾数不为全0,这些数是非规范数,即尾数部分假设前面为0,而不是1。此时指数取最后一位为1时的值,64位双精度浮点数格式中为-1022
      • 指数全1,尾数全0,表示无穷大,即Infinity。根据符号位不同可以分为+Infinity-Infinity
      • 指数全1,尾数不为全0,表示NaN,即Not a Number,不是数。
  • 尾数部分:52位,二进制只有0和1,一个数值最终都可用 1.xxx * 2 e 表示,故尾数部分表示小数点后的部分,小数点前默认有1。

0.1 + 0.2 = 0.30000000000000004

JS使用双精度 64 位二进制格式存储数值,所以先要将0.1和0.2转换为二进制。

0.1和0.2转换为二进制后是无限循环的,但是存储位是有限的,所以超出的部分要作“零舍一入”,这是第一步精度丢失

相加时,由于两数指数级不相等,要进行“对位”操作,而且相加后产生了进位,这两个原因导致存储位不够用,超出部分又要“零舍一入”,这是第二次精度丢失,导致计算结果出现偏差。

具体计算分析过程参考 IEEE754 浮点数格式 与 Javascript number 的特性

Number上各个静态常量的理解

指数不取全0或全1的原因详见文章上方指数部分详细讲解

Number.MAX_VALUENumber.MIN_VALUE

Number.MAX_VALUE:可表示的最大正值: 当符号位为0、指数位除最后一位全为1、尾数位全为1时,为可表示的最大正值。 Number.MIN_VALUE:可表示的最小正值: 当符号位为0、指数位全为0、尾数最后一位为1时,为可表示的最小正值。

// Number.MAX_VALUE
expect((2 - Math.pow(2, -52)) * Math.pow(2, 1023)).toBe(Number.MAX_VALUE)
// Number.MIN_VALUE
expect(Math.pow(2, -52) * Math.pow(2, -1022)).toBe(Number.MIN_VALUE)

Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER

Number.MAX_SAFE_INTEGER:可表示的最大准确整数: 当符号位为0、指数实际值为52、尾数位全为1,即尾数每一位都表示整数时,为可表示的最大准确整数。 Number.MIN_SAFE_INTEGER:可表示的最小准确整数: 当符号位为1、指数实际值为52、尾数位全为1,即尾数每一位都表示整数时,为可表示的最小准确整数。

// Number.MAX_SAFE_INTEGER
expect((2 - Math.pow(2, -52)) * Math.pow(2, 52)).toBe(Number.MAX_SAFE_INTEGER)
// Number.MIN_SAFE_INTEGER
expect((-1) * (2 - Math.pow(2, -52)) * Math.pow(2, 52)).toBe(Number.MIN_SAFE_INTEGER)

Number.EPSILON

大于1的最小可表示数与1的差:符号位为0,指数实际值为0,尾数最后一位为1时的数减去1,为Number.EPSILON。即 2-52

// Number.EPSILON
expect((1 + Math.pow(2, -52)) - 1).toBe(Number.EPSILON)

logan70 avatar Nov 18 '19 03:11 logan70