1. 程式人生 > >JS浮點計算精度問題分析與解決

JS浮點計算精度問題分析與解決

href 動態控制 hub 截斷 max fix math floating 方案

問題描述

在JS計算四則運算時會遇到精度丟失的問題,會引起諸多問題,看看以下例子:
例如:在chrome控制臺輸入 0.1 + 0.7
輸出結果是 0.7999999999999999
例如:0.1+0.2
輸出結果:0.30000000000000004
例如:0.1277*1000000000000
輸出結果:127700000000.00002

問題代碼

bMsg.expectRate的值為0.1277
bMsg.expectRate * 1000000000000 / 10000000000
如問題描述的輸出結果可以看到,結果並不是預期的12.77,而是127700000000.00002

問題原因分析

問題原因是Javascript采用了IEEE-745浮點數表示法(幾乎所有的編程語言都采用),這是一種二進制表示法,可以精確地表示分數,比如1/2,1/8,1/1024。遺憾的是,我們常用的分數(特別是在金融的計算方面)都是十進制分數1/10,1/100等。二進制浮點數表示法並不能精確的表示類似0.1這樣 的簡單的數字。

二進制浮點數表示

0.1 + 0.2計算過程會轉化成二進制,但由於換算為二進制表達時是無窮的。例如:
0.1 -> 0.0001100110011001...(無限)
0.2 -> 0.0011001100110011...(無限)
IEEE 754 標準的 64 位雙精度浮點數的小數部分最多支持 53 位二進制位,所以兩者相加之後得到二進制為
0.0100110011001100110011001100110011001100110011001100
因浮點數小數位的限制而截斷的二進制數字,再轉換為十進制,就成了 0.30000000000000004。所以在進行算術計算時會產生誤差。

解決方案

為了解決浮點數運算精度丟失問題,一般有以下的方案:

使用類庫

  • math.js
  • decimal.js

toFixed方法

在IE6、7、8版本會存在兼容性問題,返回值不準確。

小數部分轉化為整數,計算後再轉化成小數(推薦使用)

以下是一些封裝好的方法

加法函數

/**
 ** 加法函數,用來得到精確的加法結果
 ** 說明:javascript的加法結果會有誤差,在兩個浮點數相加的時候會比較明顯。這個函數返回較為精確的加法結果。
 ** 調用:accAdd(arg1,arg2)
 ** 返回值:arg1加上arg2的精確結果
 **/
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;
}
 
//給Number類型增加一個add方法,調用起來更加方便。
Number.prototype.add = function (arg) {
    return accAdd(arg, this);
};

減法函數

/**
 ** 減法函數,用來得到精確的減法結果
 ** 說明:javascript的減法結果會有誤差,在兩個浮點數相減的時候會比較明顯。這個函數返回較為精確的減法結果。
 ** 調用:accSub(arg1,arg2)
 ** 返回值:arg1加上arg2的精確結果
 **/
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;
    return ((arg1 * m - arg2 * m) / m).toFixed(n);
}
 
// 給Number類型增加一個mul方法,調用起來更加方便。
Number.prototype.sub = function (arg) {
    return accMul(arg, this);
};

乘法函數

/**
 ** 乘法函數,用來得到精確的乘法結果
 ** 說明:javascript的乘法結果會有誤差,在兩個浮點數相乘的時候會比較明顯。這個函數返回較為精確的乘法結果。
 ** 調用:accMul(arg1,arg2)
 ** 返回值:arg1乘以 arg2的精確結果
 **/
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);
}
 
// 給Number類型增加一個mul方法,調用起來更加方便。
Number.prototype.mul = function (arg) {
    return accMul(arg, this);
};

除法函數

/** 
 ** 除法函數,用來得到精確的除法結果
 ** 說明:javascript的除法結果會有誤差,在兩個浮點數相除的時候會比較明顯。這個函數返回較為精確的除法結果。
 ** 調用:accDiv(arg1,arg2)
 ** 返回值:arg1除以arg2的精確結果
 **/
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);
    }
}
 
//給Number類型增加一個div方法,調用起來更加方便。
Number.prototype.div = function (arg) {
    return accDiv(this, arg);
};

更多參考

  • 浮點數的二進制表示
  • 浮點數前世今生
  • IEEE754
  • 浮點數運算問題
  • 格式浮點數

JS浮點計算精度問題分析與解決