js浮點數計算

由於二進制精度影響,JS在計算浮點數時容易造成偏差,而這個問題在很多程序語言中都存在,所以部分程序語言專門提供了高精度函數,爲的就是解決浮點數據計算。


JS 中貌似還沒有出現這類函數庫,因此在浮點數計算中,很容易出現精度問題,當然JS是弱類型語言,很多時候程序自己轉類型因此不注意容易忽視,比如:

alert(0.57*100)

wKioL1aZp_OxZBcrAAAHNU-G_TY923.png


這種精度問題只會在浮點數中出現,也就是說只要把浮點數提取爲成整數計算就可以避免這個問題:


  1. 當拆分爲整數時,注意借位進位,正負數處理,相對於說會減小計算的數量大小,能更好的精確,但處理過程複雜。

  2. 轉成對等整數計算,不處理進位,正負數,只需要處理好小數點位數,計算數量偏小,處理方便,大數據量不宜使用。


下面爲第二種方法解決浮點數計算的代碼:(程序未進行嚴格測試,如有問題歡迎留言!

(function (win) {
    var parameToString = function (parame) {//轉字符串
        return typeof parame === 'string' ? parame : parame.toString();
    }, parameSplit = function (parame) {//拆分浮點數
        var split = parameToString(parame).split('.', 2), integer, decimal = '';
        integer = parameToNumber(split[0]);
        if (split.length === 2) {
            decimal = split[1];
        }
        return [integer, decimal];
    }, parameToNumber = function (parame) {//轉數值
        parame = parseInt(parame);
        return typeof parame === 'number' ? parame : 0;
    }, parameCompleteAfterNumber = function (parame, length) {//數字後補齊
        if (length > 0) {
            return parame + Math.pow(10, length - parame.length).toString().substr(1);
        }
        return '';
    }, parameCompleteBeforeNumber = function (parame, length) {//數字前補齊
        if (parame.length < length) {
            var insufficient = length - parame.length;
            return parameToString(insufficient * 10).substr(1) + parame;
        } else if (parame.length === length) {
            return parame;
        }
        return false;
    }, resultSplit = function (parame, decimalLength) {//結果小數拆分
        if (decimalLength === 0) {
            return integer;
        }
        var parames = parameSplit(parame), integer = parameToString(parames[0]), result;
        if (integer.length > decimalLength) {
            var diff = integer.length - decimalLength;
            result = integer.substr(0, diff) + '.' + integer.substr(diff);
        } else if (integer.length === decimalLength) {
            result = '0.' + integer;
        } else {
            result = '0.' + parameCompleteBeforeNumber(integer, decimalLength);
        }
        return Number(result + parames[1]);
    }, parameCompleteParse = function (parame1, parame2) {//參數補全解析處理
        parame1 = parameSplit(parame1);
        parame2 = parameSplit(parame2);
        var length = Math.max(parame1[1].length, parame2[1].length);
        return [parameToNumber(parame1[0] + parameCompleteAfterNumber(parame1[1], length)), parameToNumber(parame2[0] + parameCompleteAfterNumber(parame2[1], length)), length];
    }, math = {
        //加法計算
        bcadd: function (parame1, parame2) {
            var parames = parameCompleteParse(parame1, parame2), result = parames[0] + parames[1];
            return resultSplit(result, parames[2]);
        },
        //比較兩個數字
        bccomp: function (parame1, parame2) {
            var parames = parameCompleteParse(parame1, parame2);
            if (parames[0] > parames[1]) {
                return 1;
            } else if (parames[0] === parames[1]) {
                return 0;
            } else {
                return -1;
            }
        },
        //除法計算
        bcdiv: function (parame1, parame2) {
            var parames = parameCompleteParse(parame1, parame2);
            return parames[0] / parames[1];
        },
        //取模
        bcmod: function (parame, modulus) {
            var parames = parameCompleteParse(parame, modulus), result = parames[0] % parames[1];
            if (parames[2] > 0) {
                return resultSplit(result, parames[2]);
            }
            return result;
        },
        //乘法計算
        bcmul: function (parame1, parame2) {
            var parames = parameCompleteParse(parame1, parame2), result = parames[0] * parames[1];
            if (parames[2] > 0) {
                return resultSplit(result, parames[2] * 2);
            }
            return result;
        },
        //二次方根
        bcsqrt: function (parame) {
            var split = parameSplit(parame), length = split[1].length, result;
            if (length > 0) {
                length += length % 2;
                parame = Number(split[0] + parameCompleteAfterNumber(split[1], length));
            }
            result = Math.sqrt(parame);
            if (length > 0) {//小數點向前移動
                return resultSplit(result, length / 2);
            }
            return result;
        },
        //減法
        bcsub: function (parame1, parame2) {
            var parames = parameCompleteParse(parame1, parame2), result = parames[0] - parames[1];
            return resultSplit(result, parames[2]);
        }
    };
    for (var key in math) {
        win[key] = math[key];
    }
})(window);

//使用
alert(bcmul(0.57, 101.1));
alert(bcmod(2.1, 0.5));
alert(bcsqrt(0.024));



是什麼原因造成的?最主要原因是計算機只識別二進制數,所有數據都會轉成二進制數運算得來,並不是程序不會計算小數形式的二進制數,而是在程序中進制數轉換造成的。


十進制轉二進制:

整數部分(除2取餘,逆序排列):

    即不斷整除2,取餘數(0或1)爲二進制數,商再除2如此循環(直到商爲0),把得出來的餘數(二進制數)以先後計算倒序取出餘數,即爲對應二進制數。如:

    10 轉二進制結果是:

    10/2 = 5  餘  0

     5/2 = 2 餘  1

     2/2  =1 餘  0

     1/2  =0  餘  1

以先後計算倒序取出餘數則結果爲: 1010


小數部分(乘2取整,順序排列):

    即不斷乘2取整數部分(0或1),取結果中小數部分再乘2如此循環(直到結果無小數部分,當然大部分無法計算到無小數部分,所以就會出現截斷,最終影響計算精度),把得出來的整數(二進制數)以先後計算順序取出,即爲對應小數二進制數。如:

   0.35 轉二進制結果是:

    0.35*2  = 0.7  整0小0.7

    0.7*2  = 1.4  整1小0.4

    0.4*2  =  0.8  整0小0.8    ---無限循環開始

    0.8*2  = 1.6   整1小0.6

    0.6*2  = 1.2  整1小0.2

    0.2*2  = 0.4  整0小0.4     --- 循環結束

    0.4*2  = 0.8  整0小0.8

    0.8*2  = 1.6  整1小0.6

    0.6*2  = 1.2  整1小0.2

    0.2*2  = 0.4  整0小0.4

    0.4*2  = 0.8  整0小0.8

    .

    .

    .

以先後計算順序取出整數則結果爲:0.01011001100.....   截取長度取決於程序支持的長度。



二進制轉十進制:

按權相加2n,個位爲n=0,小數部分n-1,整數(個位以上)部分n+1,乘以對應位二進制數。如:

1101.101 轉十進制結果是:

23*1 + 22*1 +21*0 + 20*1 + 2-1*1 + 2-2*0  + 2-3*1 = 8 + 4 + 0 + 1 + 0.5 + 0 + 0.125 = 13.625



注:浮點數從轉換中可以看出來二進制數轉十進制數不存在精確度丟失,而十進制數轉二進制數很容易造成精確度丟失,從而影響計算結果精度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章