如何判断 0.1 + 0.2 与 0.3 相等

本文最后更新于:2 年前

1
2
3
function DeviationValue(num1, num2) {
return Math.abs(num1 - num2) < Number.EPSILON
}

像 ECMAScript 采用的就是双精确度,也就是说,会用 64 位字节来储存一个浮点数。
转换为二进制在计算但是小数无限延伸,二进制截取 53 位导致精度丢失。这就是 0.1+0.2 不为 0.3 的原因 Number.EPSILON 的精度是 2^-52,所以只要丢失精度小于 Number.EPSILON 基本可以确认相等。

作为一道面试题,我觉得重要的是要讲出一点其他人一般不会答出来的深度。像这道题,可以从原理和解决方案两个地方作为答题点,最好在编一个案例。大致讲自己遇到过这个问题,于是很好奇深入研究了一下,发现是浮点数精度导致……原理怎样怎样……然后又看了业界的库的源码,然后怎样怎样解决。
关于原理,JavaScript 深入之浮点数精度来解释,实际回答的时候,我觉得答出来

  1. 非是 ECMAScript 独有
  2. IEEE754 标准中 64 位的储存格式,比如 11 位存偏移值
  3. 其中涉及的三次精度丢失

就已经 OK 了。
再讲解决方案,这个可以直接搜索到,各种方案都了解一下,比较一下优劣,还可以参考业界的一些库的实现,比如 math.js,不过相关的我并没有看过,后面我会研究一下。
如果还有精力的话,可以从加法再拓展讲讲超出安全值的数字的计算问题。
所以我觉得一能回答出底层实现,二能回答出多种解决方案的优劣,三能拓展讲出 bignum 的问题,就是一个非常完美的回答了。

1
0.1 + 0.2 === 0.30000000000000004

JavaScript 使用 Number 类型表示数字(整数和浮点数),遵循 IEEE 754 标准,通过 64 位来表示一个数字。

计算机无法直接对十进制的数字进行运算,这是硬件物理特性决定的。这样运算就分成了两个部分:先按照 IEEE 754 转成相应的二进制,然后对阶运算
0.1 和 0.2 转换成二进制后会无限循环,但是由于 IEEE 754 尾数位数的限制,需要将后面多余的位截掉。这样在进制之间的转换过程中精度已经损失。

1
2
0.0001100110011001100110011001100110011001100110011001101(0.1)
0.001100110011001100110011001100110011001100110011001101(0.2)

解决方法

1.将数字转成整数,计算完成后再转小数

2.小数点直接计算取精确小数点几位数,最后字符串再转换成浮点数

1
2
3
;(0.1 + 0.2).toFixed(5) // "0.30000"结果是字符串

parseFloat((0.1 + 0.2).toFixed(5)) // 0.3 parseFloat() 函数可解析一个字符串,并返回一个浮点数。

三方库

Math.js
big.js


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!