JS浮点数运算结果不精确的Bug解决
前⾔
最近在做项⽬的时候,涉及到产品价格的计算,经常会出现JS浮点数精度问题,这个问题,对于财务管理系统的开发者来说,是个⾮常严重的问题(涉及到钱相关的问题都是严重的问题),这⾥把相关的原因和问题的解决⽅案整理⼀下,也希望给各位提供⼀些参考。
⼀. 常见例⼦
// 加法
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.7 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001
// 减法
0.3 - 0.2 = 0.09999999999999998
1.5 - 1.2 = 0.30000000000000004
// 乘法
0.8 * 3 = 2.4000000000000004
19.9 * 100 = 1989.9999999999998
// 除法
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999
// ⽐较
0.1 + 0.2 === 0.3 // false
(0.3 - 0.2) === (0.2 - 0.1) // false
⼆. 导致原因
JavaScript 内部只有⼀种数字类型Number,也就是说,JavaScript 语⾔的底层根本没有整数,所有数字都是以IEEE-754标准格式64位浮点数形式储存,1与1.0是相同的。因为有些⼩数以⼆进制表⽰位数是⽆穷的。JavaScript会把超出53位之后的⼆进制舍弃,所以涉及⼩数的⽐较和运算要特别⼩⼼。
三. IEEE⼆进制浮点数算术标准(IEEE 754)
IEEE⼆进制浮点数算术标准(IEEE 754)是20世纪80年代以来最⼴泛使⽤的浮点数运算标准,为许多CPU与浮点运算器所采⽤。这个标准定义了表⽰浮点数的格式(包括负零-0)与反常值(denormal number)),⼀些特殊数值(⽆穷(Inf)与⾮数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍⼊规则和五种例外状况(包括例外发⽣的时机与处理⽅式)。
四. 浮点数的存储
JS的浮点数实现也是遵循IEEE 754标准,采⽤双精度存储(double precision),使⽤64位固定长度来表⽰,其中1位⽤来表⽰符号位,11位⽤来表⽰指数,52位表⽰尾数。如下图:
符号位(sign):第1位是正负数符号位,0代表正数,1代表负数
指数位(Exponent):中间11位存储指数,⽤来表⽰次⽅数
尾数位(mantissa):最后的52位是尾数,超出部分⾃动进⼀舍零
五. 浮点数的计算步骤(0.1+0.2)
【1】⾸先,⼗进制的0.1和0.2会转换成⼆进制的,但是由于浮点数⽤⼆进制表⽰是⽆穷的
0.1——>0.0001 1001 1001 1001 ...(1001循环)
0.2——>0.0011 0011 0011 0011 ...(0011循环)
【2】IEEE754标准的64位双精度浮点数的⼩数部分最多⽀持53位⼆进制,多余的⼆进制数字被截断,所以两者相加之后的⼆进制之和是
0.0100110011001100110011001100110011001100110011001101
【3】将截断之后的⼆进制数字再转换为⼗进制,就成了0.30000000000000004,所以在计算时产⽣了误差
六. 解决办法
【1】引⽤类库
Math.js
decimal.js
big.js
【2】思路⼀:在知道⼩数位个数的前提下,可以考虑通过将浮点数放⼤倍数到整型(最后再除以相应倍数),再进⾏运算操作,这样就能得到正确的结果了
0.1 + 0.2 ——> (0.1 * 10 + 0.2 * 10) / 10 // 0.3
0.8 * 3 ——> ( 0.8 * 100 * 3) / 100 //2.4
【3】⾃定义⼀个转换和处理函数
// f代表需要计算的表达式,digit代表⼩数位数
Math.formatFloat = function (f, digit) {
// Math.pow(指数,幂指数)
var m = Math.pow(10, digit);
// und()四舍五⼊
und(f * m, 10) / m;
}
console.log(Math.formatFloat(0.3 * 8, 1)); // 2.4
console.log(Math.formatFloat(0.35 * 8, 2)); // 2.8
【4】加法函数
/**
** 加法函数,⽤来得到精确的加法结果
** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会⽐较明显。这个函数返回较为精确的加法结果。
** 调⽤:accAdd(arg1,arg2)
** 返回值:arg1加上arg2的精确结果
**/
function accAdd(arg1, arg2) {
var r1, r2, m, c;
try {
r1 = String().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = String().split(".")[1].length;
} catch (e) {
r2 = 0;
}
c = Math.abs(r1 - r2);
m = Math.pow(10, Math.max(r1, r2));
if (c > 0) {
var cm = Math.pow(10, c);
if (r1 > r2) {
arg1 = String().replace(".", ""));
arg2 = String().replace(".", "")) * cm;
} else {
arg1 = String().replace(".", "")) * cm;
arg2 = String().replace(".", ""));
}
} else {
arg1 = String().replace(".", ""));
arg2 = String().replace(".", ""));
}
return (arg1 + arg2) / m;
}
//给Number类型增加⼀个add⽅法,调⽤起来更加⽅便。
Number.prototype.add = function (arg) {
return accAdd(arg, this);
};
【5】减法函数
/**
** 减法函数,⽤来得到精确的减法结果
** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会⽐较明显。这个函数返回较为精确的减法结果。
** 调⽤:accSub(arg1,arg2)
** 返回值:arg1加上arg2的精确结果
**/
function accSub(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = String().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = String().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度
n = (r1 >= r2) ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
}
// 给Number类型增加⼀个mul⽅法,调⽤起来更加⽅便。
Number.prototype.sub = function (arg) {
return accMul(arg, this);
};
【6】乘法函数
/**
** 乘法函数,⽤来得到精确的乘法结果
** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会⽐较明显。这个函数返回较为精确的乘法结果。 ** 调⽤:accMul(arg1,arg2)
** 返回值:arg1乘以 arg2的精确结果
**/
function accMul(arg1, arg2) {
var m = 0,
s1 = String(),
s2 = String();
try {
m += s1.split(".")[1].length;
} catch (e) {}
try {
m += s2.split(".")[1].length;
} catch (e) {}
return place(".", "")) * place(".", "")) / Math.pow(10, m);
}
// 给Number类型增加⼀个mul⽅法,调⽤起来更加⽅便。
Number.prototype.mul = function (arg) {
return accMul(arg, this);
};
【7】除法函数
/**
** 除法函数,⽤来得到精确的除法结果
** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会⽐较明显。这个函数返回较为精确的除法结果。 ** 调⽤:accDiv(arg1,arg2)
** 返回值:arg1除以arg2的精确结果
**/
function accDiv(arg1, arg2) {
var t1 = 0,
t2 = 0,
r1, r2;
try {
t1 = String().split(".")[1].length;
} catch (e) {}
try {
t2 = String().split(".")[1].length;
} catch (e) {}
with(Math) {
r1 = String().replace(".", ""));
r2 = String().replace(".", ""));
javascript说明return (r1 / r2) * pow(10, t2 - t1);
}
}
//给Number类型增加⼀个div⽅法,调⽤起来更加⽅便。
Number.prototype.div = function (arg) {
return accDiv(this, arg);
};
总结
以上就是这篇⽂章的全部内容了,希望本⽂的内容对⼤家的学习或者⼯作具有⼀定的参考学习价值,谢谢⼤家对的⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论