js中toFixed精度問題的原因及解決辦法


toFixed() 方法可把 Number 四舍五入為指定小數位數的數字。例如將數據Num保留2位小數,則表示為:toFixed(Num);但是其四舍五入的規則與數學中的規則不同,使用的是銀行家舍入規則,銀行家舍入:所謂銀行家舍入法,其實質是一種四舍六入五取偶(又稱四舍六入五留雙)法。具體規則如下:簡單來說就是:四舍六入五考慮,五后非零就進一,五后為零看奇偶,五前為偶應舍去,五前為奇要進一。

經測試發現,在chorme下面,並沒有完全遵守這個規則,尤其是5的后面沒有數字的時候,不是這么判斷的,如下:

var b = 1.335

b.toFixed(2)

"1.33"

var b = 1.345

b.toFixed(2)

"1.34"

var b = 1.355

b.toFixed(2)

"1.35"

var b = 1.365

b.toFixed(2)

"1.36"

var b = 1.375

b.toFixed(2)

"1.38"

var b = 1.385

b.toFixed(2)

"1.39"

 

可以發現在chorme下沒有完全去遵循這個規律,或許它有自己的算法,但是畢竟它沒有遵循通用的銀行家算法,所以tofixed這個方法在涉及到金錢計算的業務中還是少用.

總而言之:不論引入toFixed解決浮點數計算精度缺失的問題也好,它有沒有使用銀行家舍入法也罷,都是為了解決精度的問題,但是又離不開二進制浮點數的環境,但至少他幫助我們找到了問題所在,從而讓我們有解決方法。

 

一開始的辦法是把要四舍五入的后一位單獨拎出來單獨判斷。

解決方法:

通過重寫toFixed方法:

Number.prototype.toFixed = function (n) {

let result = number.toString();

const arr = result.split('.');

const integer = arr[0];

const decimal = arr[1];

result = integer + '.' + decimal.substr(0, n);

const last = decimal.substr(n, 1);

 

// 四舍五入,轉換為整數再處理,避免浮點數精度的損失

if (parseInt(last, 10) >= 5) {

const x = Math.pow(10, n);

result = ((parseFloat(result) * x) + 1) / x;

result = result.toFixed(n);

}

return result;

}

 

然后又發現計算機二進制編碼導致的精度問題,詳見上一篇博客。

自己debugger,發現頁面中的js進了死循環。很明顯問題出在toFixed中回調了toFixed,結果沒有走出來,繼續debugger,又有了驚人的發現。以下是控制台測試:

console.log(2.115 * 100) // 211.50000000000003

console.log(2.0115 * 1000) // 2011.4999999999998

 

既然你一直進入循環,我就手動把你拉出來。

result = (Math.round((parseFloat(result)) * x) + 1) / x;

 

最終完整的重寫toFixed的方法

// toFixed兼容方法

Number.prototype.toFixed = function (n) {

    if (n > 20 || n < 0) {

        throw new RangeError('toFixed() digits argument must be between 0 and 20');

    }

    const number = this;

    if (isNaN(number) || number >= Math.pow(10, 21)) {

        return number.toString();

    }

    if (typeof (n) == 'undefined' || n == 0) {

        return (Math.round(number)).toString();

    }

 

    let result = number.toString();

    const arr = result.split('.');

 

    // 整數的情況

    if (arr.length < 2) {

        result += '.';

        for (let i = 0; i < n; i += 1) {

            result += '0';

        }

        return result;

    }

 

    const integer = arr[0];

    const decimal = arr[1];

    if (decimal.length == n) {

        return result;

    }

    if (decimal.length < n) {

        for (let i = 0; i < n - decimal.length; i += 1) {

            result += '0';

        }

        return result;

    }

    result = integer + '.' + decimal.substr(0, n);

    const last = decimal.substr(n, 1);

 

    // 四舍五入,轉換為整數再處理,避免浮點數精度的損失

    if (parseInt(last, 10) >= 5) {

        const x = Math.pow(10, n);

        result = (Math.round((parseFloat(result) * x)) + 1) / x;

        result = result.toFixed(n);

    }

    return result;

}

 


免責聲明!

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



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