各位老司机应该知道,很多时候做小数运算的时候会出现小数溢出。常见的场景是做价格运算,测试经常报bug。
于是针对这个奇特的问题,开始了寻根之旅。
举个例子:0.2+0.1=0.30000000000000004
为什么会这样?
其实很多其他计算机语言都有这个问题。计算机中的所有运算都会经过二进制转换再计算的。
0.2的二进制是
0.2 * 2 = 0.4 取整数位 0 得 0.0
0.4 * 2 = 0.8 取整数位 0 得 0.00
0.8 * 2 = 1.6 取整数位 1 得 0.001
0.6 * 2 = 1.2 取整数位 1 得 0.0011
0.2 * 2 = 0.4 取整数位 0 得 0.00110
(n)….
太长了 于是直接在控制台上打印
var num = 0.2;
num.toString(2)
// "0.001100110011001100110011001100110011001100110011001101"
同理算出 0.1的二进制为
// "0.0001100110011001100110011001100110011001100110011001101"
两个二进制相加得出
// "0.0100110011001100110011001100110011001100110011001100111"
根据这个IEEE754的标准 就会把最后的 1 舍去变成
0.0100110011001100110011001100110011001100110011001101
然后转成十进制得出结果是 0.30000000000000004
再举个例子 0.1+0.3 = 0.4
为什么这两个数不会产生溢出呢?
算出 0.1的二进制为
// "0.0001100110011001100110011001100110011001100110011001101"
算出 0.4的二进制为
// "0.01100110011001100110011001100110011001100110011001101"
两个二进制相加得出
// "0.1000000000000000000000000000000000000000000000000000001"
根据这个IEEE754的标准 就会把最后的第52位进一舍零,得到
"0.1000000000000000000000000000000000000000000000000000"
剩下0.1 转换十进制为0.5
找了网上的一张图,其实还不是完全看懂指数位是什么。。。。
怎么处理?
/**
* 加法运算,避免数据相加小数点后产生多位数和计算精度损失。
*
* @param a加数1 | b加数2
*/
function Add(a, b) {
var n, n1, n2;
try {
n1 = a.toString().split(".")[1].length;
} catch (e) {
n1 = 0;
}
try {
n2 = b.toString().split(".")[1].length;
} catch (e) {
n2 = 0;
}
n = Math.pow(10, Math.max(n1, n2));
return (a * n + b * n) / n;
}
/**
* 减法运算,避免数据相减小数点后产生多位数和计算精度损失。
*
* @param a被减数 | b减数
*/
function Sub(a, b) {
var n, n1, n2;
var precision; // 精度
try {
n1 = a.toString().split(".")[1].length;
} catch (e) {
n1 = 0;
}
try {
n2 = b.toString().split(".")[1].length;
} catch (e) {
n2 = 0;
}
baseNum = Math.pow(10, Math.max(n1, n2));
n = Math.pow(10, Math.max(n1, n2));
return (a * n – b * n) / n;
}
/**
* 乘法运算,避免数据相乘小数点后产生多位数和计算精度损失。
*
* @param a被乘数 | b乘数
*/
function Mul(a, b) {
var n1 = 0,
n2 = 0;
try {
n1 += a.toString().split(".")[1].length;
} catch (e) {}
try {
n2 += b.toString().split(".")[1].length;
} catch (e) {}
return (a * Math.pow(10, n1)) * (b * Math.pow(10, n2)) / Math.pow(10, n1 + n2);
}
/**
* 除法运算,避免数据相除小数点后产生多位数和计算精度损失。
*
* @param a被除数 | b除数
*/
function Div(a, b) {
var n1 = 0,
n2 = 0;
var baseNum3, baseNum4;
try {
n1 += a.toString().split(".")[1].length;
} catch (e) {}
try {
n2 += b.toString().split(".")[1].length;
} catch (e) {}
return (a * Math.pow(10, n1)) / (b * Math.pow(10, n2)) / Math.pow(10, n1 – n2);
}
// js小数相加,精确的结果应当是10090.701
var numAdd = Add(9856.556, 234.145);
// js小数相减,精确的结果应当是422.441
var numSub = Sub(656.55, 234.109);
// js小数相乘,精确的结果应当是51.285
var numMul = Mul(Mul(34.19, 0.03), 50);
// js小数相除,精确的结果应当是40.0936
var numDiv = Div(100.234, 2.5);
思想是计算出有几位小数(n),先转化成整数,保证用整数进行计算,再除以 10*n;