js處理浮點數計算誤差

北辰狼月發表於2018-12-07

眾所周知,浮點計算會產生舍入誤差的問題,比如,0.1+0.2,結果應該是0.3,但是計算的結果並不是如此,而是0.30000000000000004,這是使用基於IEEE754數值的浮點計算的通病,js並非獨此一家,今天我們就來看看js怎麼解決這個誤差的。
以下是針對加減乘除的解決方法:
加法:
function accAdd(arg1, arg2) {
var r1, r2, m, c;
try {
r1 = arg1.toString().split(".")[1].length;
}
catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().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 = Number(arg1.toString().replace(".", "")); //轉化成數字
arg2 = Number(arg2.toString().replace(".", "")) * cm;
} else {
arg1 = Number(arg1.toString().replace(".", "")) * cm;
arg2 = Number(arg2.toString().replace(".", ""));
}
} else { //位數相等
arg1 = Number(arg1.toString().replace(".", ""));
arg2 = Number(arg2.toString().replace(".", ""));
}
return (arg1 + arg2) / m;
}
減法:
function accSub(arg1, arg2) {
var r1, r2, m, n;
try {
r1 = arg1.toString().split(".")[1].length;
}
catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
}
catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //動態控制精度長度
n = (r1 >= r2) ? r1 : r2; //取位數大的
// n = Math.max(r1, r2);
return ((arg1 * m - arg2 * m) / m).toFixed(n);
}
乘法:
function accMul(arg1, arg2) { //去小數點擴大後,再除以相應倍數
var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
try {
m += s1.split(".")[1].length;
}
catch (e) {
}
try {
m += s2.split(".")[1].length;
}
catch (e) {
}
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
}
除法:
function accDiv(arg1, arg2) { //去小數點,擴大倍數,再乘以相應倍數
var t1 = 0, t2 = 0, r1, r2;
try {
t1 = arg1.toString().split(".")[1].length;
}
catch (e) {
}
try {
t2 = arg2.toString().split(".")[1].length;
}
catch (e) {
}
with (Math) {
r1 = Number(arg1.toString().replace(".", ""));
r2 = Number(arg2.toString().replace(".", ""));
return (r1 / r2) * pow(10, t2 - t1);
}
}
用以上4個方法進行計算的話,就能夠規避浮點數計算誤差問題了,他們的原理大多是,通過去掉小數點,將浮點數變為整數,再進行計算。比如加法:
var r1, r2, m, c; //r1為arg1的小數位數,r2為arg2的小數位數,m為arg1和arg2較大數的冪,c是位數差
try {
r1 = arg1.toString().split(".")[1].length; //計算arg1的位數
}
catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length; //計算arg2的位數
}
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 = Number(arg1.toString().replace(".", "")); //轉化成數字
arg2 = Number(arg2.toString().replace(".", "")) * cm; //較小數乘以位數差的冪
} else {
arg1 = Number(arg1.toString().replace(".", "")) * cm;
arg2 = Number(arg2.toString().replace(".", ""));
}
} else { //位數相等
arg1 = Number(arg1.toString().replace(".", ""));
arg2 = Number(arg2.toString().replace(".", ""));
}
return (arg1 + arg2) / m; //將擴大了的數相加,再除以較大數的冪,將結果還原
}
從上面可以看出,為了的到兩個小數擴大後的整數,使用的是現將他們轉換成字串,再用空去替換點號,這個方法得到的數字就是擴大後的數字,而網上流行另一種寫法,就是先乘以一個倍數,將其變為整數,再相加,再除以倍數的方法,這個方法乍一看,是行的通的,但是經過測試,此方法也會存在bug。
function add(a, b) {
var c, d, e, h;
try {
c = a.toString().split(".")[1].length;
} catch (f) {
c = 0;
}
try {
d = b.toString().split(".")[1].length;
} catch (f) {
d = 0;
}
e = Math.pow(10, Math.max(c, d));
return (a * e + b * e) / e;
//h = Math.max(c, d);
//return ((mul(a, e) + mul(b, e)) / e).toFixed(h);
}
此方法e指的是兩個數中較大數的冪,錯就錯在,ae,和be在計算的時候會出現計算誤差,由於wiki不能上傳圖片,就不展示了,綜上所述,我推薦第一種的解決方案。當然第二種也是有不就措施的,就是將我上面程式碼的兩行註釋放開,我們運用了toFixed將誤差給擷取,從而得到正確的結果。
這裡,我在提供一個測試的方法:
function test() {
var a = (Math.random() * 100).toFixed(2) - 0;
var b = (Math.random() * 1000).toFixed(2) - 0;
var result = add(a, b);
if ((result + '').length > 10) {
console.error('被加數:' + a, '加數:' + b, '結果:' + result);
return;
}
setTimeout(function () {
test();
}, 10);
}

相關文章