持续创作,加速成长!这是我参与「掘金日新计划 · 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 解决的是整数问题。
参考资料
- JavaScript 深入之浮点数精度:github.com/mqyqingfeng…
- 0.1 + 0.2为什么不等于0.3?:juejin.cn/post/694787…
- math.js:github.com/josdejong/m…
- big.js:github.com/MikeMcl/big…
- number-precision:github.com/nefe/number…
- proposal-decimal 提案: github.com/tc39/propos…
浏览知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。