JavaScript 中的小数精度问题与解决方式
- Published on
- — 571 Words
- Authors
- Name
- Richard Shih
- @realshiddong
前端同学可能都遇见过下面这个的问题,这也是面试中经常可能出现的问题,两边是不相等的,为什么呢?
0.1 + 0.2 === 0.3 // false
隐藏在背后的原因是很有趣的,且看下文。
小数如何用二进制表示
给出一个任意的实数,我们基本都清楚整数部分如何使用二进制表示,那小数部分呢?
实际上,小数部分的表示方式是这样的:
- 将小数部分 * 2,取出结果中整数部分作为二进制表示的第 1 位
- 再将结果中小数部分 * 2,将得到的结果中的整数部分作为二进制表示的第 2 位
- 以此类推,直到小数部分为 0
下面是 0.1 的计算过程:
step 1. 0.1 * 2 = 0.2 ———— 0
step 2. 0.2 * 2 = 0.4 ———— 0
step 3. 0.4 * 2 = 0.8 ———— 0
step 4. 0.8 * 2 = 1.6 ———— 1
step 5. 0.6 * 2 = 1.2 ———— 1
step 6. 0.2 * 2 = 0.4 ———— 0 # 开始循环
...
可以发现,在小数 0.1 的计算中已经出现了循环, 0.1 的二进制表示为 0001 1001 1001 ...
如果包含整数与小数,如 10.1,其二进制表示结果为 1010.0001 1001 1001 ...
JavaScript 中的精度问题
在 JavaScript 中不是真正的整数,number 类型实际上是浮点型,使用的是双精度格式 —— 即 64 位二进制。
回到最开始的问题,由于 0.1 和 0.2 的二进制表示结果存在循环,使用 64 位二进制必然导致不能完整的表示它们,当它们相加时,其结果并不会时精确的 0.3,而是一个非常接近的值。
>> 0.1 + 0.2
0.30000000000000004
如何解决呢
最常见的做法是我们可以认为在极小的容差范围内,它们被认为是相等。这个值可以称为“机械极小值” —— machine epsilon。
对于 JavaScript 来说,这个值可以用 Number.EPSILON
表示。这个值通常是 2^-52 (2.220446049250313e-16)
// polyfill
if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52)
}
function numbersCloseEnoughToEqual(n1, n2) {
return Math.abs(n1 - n2) < Number.EPSILON
}
判断两个小数是否相等时,可以采用这种方式:
let res = 0.1 + 0.2
numbersCloseEnoughToEqual(res, 0.3) // true