引言
在日常的业务中我经常会遇到需要对数字进行计算处理,也就会出现一个JS中很经典的问题,0.1+0.2 ≠ 0.3。本文将对这种现象原因进行分析,以及解决方案。
为什么0.1 + 0.2 ≠ 0.3
对于一些强类型的编程语言会对数字的类型分为整数、浮点数等多种类型,但JS 只有Number类型,它是按照国际 IEEE 754标准,将数字都存储为双精度浮点数。
IEEE二进制浮点数算术标准IEEE 754是20世纪80年代以来最广泛使用的浮点数运算标准。
这种格式以 64 位存储数字,其中数字(分数)存储在位 0 到 51 中,指数存储在位 52 到 62 中,符号存储在位 63 中。
在计算机使用二进制数来进行计算,首先我们将它们转化为二进制数,根据IEEE 754的标准,最多我们只能保存53位,但由于计算百机内部以二进制保存,所以十进制的有限位的小数,在计算机内部会是一个无限位的小数
例如: 十进制的0.9虽然只有一位小数,转成2进制道是无限循环小数0.1110011001100110011...
所以多出来舍去,0舍1入。
所以在计算机中除了可以表示为2的幂次以及整数数乘的浮点数可以准确表示外,其余的数的值都是近似值。
所以我们就得到0.1 和 0.2的近似值
0.1: 0.0001100110011001100110011001100110011001100110011010
0.2: 0.0011001100110011001100110011001100110011001100110011
0.1 + 0.2: 0.0100110011001100110011001100110011001100110011001101
计算完成后,又将二进制数转化为浮点数
这就是 0.1 + 0.2 = 0.30000000000000004 的原因。
怎么让0.1 + 0.2 = 0.3
手写
解决计算精度主要思路就是通过将浮点数计算都转化为整数计算,最后再除以扩大的倍数,来确保计算的正确性。使用这种方式要注意JS中的最大的安全数
MAX_SAFE_INTEGER: 9007199254740991
MIN_SAFE_INTEGER: -9007199254740991
// 精确加法
function add(num1, num2) {
const num1Digits = (num1.toString().split(".")[1] || "").length;
const num2Digits = (num2.toString().split(".")[1] || "").length;
const magnification = Math.pow(10, Math.max(num1Digits, num2Digits));
return (Math.floor(num1 * magnification) + Math.floor(num2 * magnification)) / magnification;
}
add(1.1, 2.2) // 3.3
// 精确乘法
function multiply(num1, num2) {
const num1Digits = (num1.toString().split(".")[1] || "").length;
const num2Digits = (num2.toString().split(".")[1] || "").length;
const magnification = Math.pow(10, Math.max(num1Digits, num2Digits));
return (
(Math.floor(num1 * magnification) * Math.floor(num2 * magnification)) /
(magnification * magnification)
);
}
add(1.1, 2.2) // 2.42
开源推荐
在日常项目中,我经常使用这个库来实现JS的相关计算需求number-precision,可以满足大部分计算需求。
import NP from 'number-precision'
NP.plus(0.1, 0.2); // = 0.3
也可以使用math.js来满足各种复杂的数学计算场景。