0.1+0.2 为什么不等于 0.3

148 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

核心描述

  • Number 类型: ECMAScript 使用 IEEE754 标准(IEEE 二进制浮点数算数标准)来表示整数和浮点数值。此标准规定了4种表示浮点数值的方式,单精确度(32位)、双精确度(64位)、延伸单精确度、延伸双精确度。ECMAScript 采用的就是双精确度,即64位存储一个浮点数。
  • 为什么 0.1+0.2 !== 0.3
    • 计算机底层并不会进行十进制的运算,都需要转换为二进制进行运算
    • 0.1+0.2 的运算过程中,会先将十进制转为二进制,然后用二进制的规则进行运算后,再转回十进制,在这一系列的过程中,导致了精度丢失,最终结果就是无法得到 0.3 的结果。
  • JavaScript 中浮点计算的核心步骤:
    • 十进制转二进制
    • 二进制转科学记数法
    • 内存对科学记数法表示的数据进行解析、存储
    • 对阶运算
    • 二进制加法运算
    • 舍入运算
    • 二进制转十进制
  • 在上述的很多步骤中都会存在精度丢失的情况,最终导致了 0.1+0.2 !== 0.3

知识拓展

  • ES2020 中,引入了一种新的数据类型 BigInt(大整数),只能表示整数,如果初始化时传入小数,会报错。任意位数,不受 Number 中的安全值范围限制。需要在数字后面加后缀 n,如 123n
  • parseInt 的特殊用法(第二个参数):
    • parseInt(string, radix) 的第二个参数表示要解析的数字的基数,即进制。如:默认为十进制,可以传入 2~36 之间,如果小于 2 或大于 36 则会返回 NaN
    • [1,2,3].map(parseInt) : 输出结果为 1,NaN,NaN
      • 因为 map(callback) 方法传入的回调 callback 默认会有3个入参,分别为 callback(currentValue, index, array)
      • currentValue 表示数组当前遍历的值
      • index 表示当前遍历的索引
      • array 表示当前遍历的数组本身
      • 因此对于 parseInt 方法而言,每一次执行的入参均不同
      • 当前 currentValue 为 1 时,parseInt(1,0),第二个参数为0表示十进制,所以返回 1
      • 当前 currentValue 为 2 时,parseInt(2,1),第二个参数为1,不符合 2~36 的限制,所以返回 NaN
      • 当前 currentValue 为 3 时,parseInt(3,2),第一个参数不是二进制数,所以返回 NaN
  • 如何可以实现精确计算
    • 先乘以 10 ,变成整数后再除以 10
    • 利用第三方库:
      • Math.js
      • big.js
      • number-precision
  • 还有哪些项目中常见的问题
    • 业务中前后端传递 LongLong 类型的数值时,也会精度丢失
    • 前端计算订单金额等比较重要的金融运算时,并不安全,需要后端做二次校验,或干脆前端就不要做计算
  • 浮点类型的计算 0.1+0.2 !== 0.3 并非仅在 JavaScript 语言中,很多语言都有此问题,包括 Java、C、C++、C#、Python 等等,更多结果,可以在此网站查看 0.30000000000000004.com
  • 在新的 TC39 的新提案中,建议 JS 引入新的原生类型:decimal(后缀 m),声明这个数字是十进制,从来解决 JS 的小数问题,正好类似于 BigInt 解决的是整数问题。

参考资料

浏览知识共享许可协议

本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。