JS精度丟失導致的問題及解決


遇到的問題:
項目中出現了 17652.19 + 7673.78 - 25325.97 = -3.64 的問題,最后發現是JS精度丟失的問題,那么就先來看看這個結果是怎么產生的。

產生原因:
JavaScript 中所有數字包括整數和小數都只有一種類型 — Number。它的實現遵循 IEEE 754 標准,使用 64 位固定長度來表示,也就是標准的 double 雙精度浮點數(相關的還有float 32位單精度)。為什么呢,因為這樣節省存儲空間。

也就是說 17652.19 + 7673.78 = 25325.969999999998。其實最簡單的例子是 0.1+0.2 = 0.30000000000000004

0.1的二進制表示的是一個無限循環小數,該版本的 JS 采用的是浮點數標准需要對這種無限循環的二進制進行截取,從而導致了精度丟失,造成了0.1不再是0.1,截取之后0.1變成了 0.100…001,0.2變成了0.200…002。所以兩者相加的數大於0.3。

將0.1轉換成為二進制加上0.2的二進制會是53位,但是二進制的最大位數是52位取近似值。

那么還有 0.1+0.2 -0.3 = 5.6是什么問題呢?

因為在輸入內容進行轉換的時候,二進制轉換成十進制,然后十進制轉換成字符串,在這個轉換的過程中發生了取近似值,所以打印出來的是一個近似值。

實際上取到的值是

0.1+0.2-0.3 = 5.551115123125783e-17

在頁面上取兩位小數的時候,會直接從字符串中讀取,會出現值變成 5.56

解決
toFixed()
因為toFixed() 進行並轉換之后是string類型的,需要在進行強制Number() 轉換

Number((0.1+0.2).toFixed(2))

實際是有很多的問題的,存在兼容性問題,在chrome瀏覽器上出現了

335.toFixed(2) = '1.33'

所以需要處理一下兼容性問題,在網上找了一下,有這樣的處理辦法:

通過判斷最后一位是否大於等於5來決定需不需要進位,如果需要進位先把小數乘以倍數變為整數,加1之后,再除以倍數變為小數,這樣就不用一位一位的進行判斷。

// toFixed兼容方法
Number.prototype.toFixed = function(len){
    if(len>20 || len<0){
        throw new RangeError('toFixed() digits argument must be between 0 and 20');
    }
    // .123轉為0.123
    var number = Number(this);
    if (isNaN(number) || number >= Math.pow(10, 21)) {
        return number.toString();
    }
    if (typeof (len) == 'undefined' || len == 0) {
        return (Math.round(number)).toString();
    }
    var result = number.toString(),
        numberArr = result.split('.');

    if(numberArr.length<2){
        //整數的情況
        return padNum(result);
    }
    var intNum = numberArr[0], //整數部分
        deciNum = numberArr[1],//小數部分
        lastNum = deciNum.substr(len, 1);//最后一個數字
    
    if(deciNum.length == len){
        //需要截取的長度等於當前長度
        return result;
    }
    if(deciNum.length < len){
        //需要截取的長度大於當前長度 1.3.toFixed(2)
        return padNum(result)
    }
    //需要截取的長度小於當前長度,需要判斷最后一位數字
    result = intNum + '.' + deciNum.substr(0, len);
    if(parseInt(lastNum, 10)>=5){
        //最后一位數字大於5,要進位
        var times = Math.pow(10, len); //需要放大的倍數
        var changedInt = Number(result.replace('.',''));//截取后轉為整數
        changedInt++;//整數進位
        changedInt /= times;//整數轉為小數,注:有可能還是整數
        result = padNum(changedInt+'');
    }
    return result;
    //對數字末尾加0
    function padNum(num){
        var dotPos = num.indexOf('.');
        if(dotPos === -1){
            //整數的情況
            num += '.';
            for(var i = 0;i<len;i++){
                num += '0';
            }
            return num;
        } else {
            //小數的情況
            var need = len - (num.length - dotPos - 1);
            for(var j = 0;j<need;j++){
                num += '0';
            }
            return num;
        }
    }
}

一些類庫

math.js,
decimal.js,
D.js
ES6在Number對象上新增了一個極小的常量——Number.EPSILON

Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"

原文:https://blog.csdn.net/zr15829039341/article/details/102532033

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM