JS中toFixed()方法四舍五入的精度問題詳解
踩的坑
最近工作中,在計(jì)算一個(gè)商品的折扣價(jià)格,有時(shí)候總是出現(xiàn)價(jià)格會(huì)有一分錢的差異,涉及錢的問題都是比較敏感的,經(jīng)過排查,最后發(fā)現(xiàn)竟然是 JS 原生的 toFixed 方法的問題。
好家伙,這都啥規(guī)律啊。。。(⊙o⊙)
填坑方法
先不著急去探究其中的問題,既然發(fā)現(xiàn)了問題,那就先把 Bug 修復(fù)了先,原生方法用不了,就自己寫一個(gè)唄,還不是分分鐘的事情,哈哈哈!
/** * 保留小數(shù)點(diǎn)幾位數(shù), 自動(dòng)補(bǔ)零, 四舍五入 * @param num: 數(shù)值 * @param digit: 小數(shù)點(diǎn)后位數(shù) * @returns string */ function myFixed(num, digit) { if(Object.is(parseFloat(num), NaN)) { return console.log(`傳入的值:${num}不是一個(gè)數(shù)字`); } num = parseFloat(num); return (Math.round((num + Number.EPSILON) * Math.pow(10, digit)) / Math.pow(10, digit)).toFixed(digit); }
什么樣的坑?
好了,既然 Bug 解決完了,下面我們就來探索一下 toFixed 其中的奧秘。
呃...首先,Em...百度一下吧,面向百度編程工程師,果然一查大把結(jié)果,看來是個(gè)經(jīng)典問題了。
經(jīng)過一番了解得知,原來是 toFixed 方法采用的四舍五入,并不是我們理解的字面上的四舍五入。而 toFixed 方法它是采用一種詭異的方法 "四舍六入五取偶" ,也叫銀行家算法,這是什么個(gè)意思呢?
完整說法:"四舍六入五考慮,五后非零就進(jìn)一,五后為零看奇偶,五前為偶應(yīng)舍去,五前為奇要進(jìn)一"。
大概意思是:當(dāng)舍去位的數(shù)值 ≤4 時(shí)舍去,當(dāng)它 ≥6 時(shí)加上,可當(dāng)它 =5 時(shí),則根據(jù) 5 后面的數(shù)字來定;當(dāng) 5 后有非零數(shù)字時(shí),舍 5 入 1;當(dāng) 5 后無有效數(shù)字時(shí),需要再分兩種情況:5 前為偶數(shù),舍 5 不進(jìn);5 前為奇數(shù),舍 5 入 1 。
根據(jù)這個(gè)規(guī)則,我在瀏覽器上又跑了一些測(cè)試,但卻還是感覺不對(duì)。
// 五前是偶數(shù),沒有舍去? console.log(1.00000065.toFixed(7)); // 1.0000007 錯(cuò)誤 console.log(1.000000065.toFixed(8)); // 1.00000007 錯(cuò)誤 // 五前是奇數(shù),沒有進(jìn)一? console.log(1.00000015.toFixed(7)); // 1.0000001 錯(cuò)誤 console.log(1.000000015.toFixed(8)); // 1.00000001 錯(cuò)誤
這到底為啥?真是讓人摸不著頭腦。。。(︶︿︶)
再經(jīng)過一番探索,總算是有點(diǎn)收獲了,下面就得來看看 ECMAScript 規(guī)范對(duì)該方法的定義了,有時(shí)候回歸規(guī)范才是最靠譜的方式。
上圖是關(guān)于整個(gè) toFixed 方法的定義,不過是翻譯后的版本,會(huì)有出入但差別不大,也可以點(diǎn)擊上面的鏈接查看原文,我們主要關(guān)注圖中紅框部分,通過公式來計(jì)算舍去位數(shù)值。
我們下面兩個(gè)來舉個(gè)栗子,測(cè)試一下。
console.log(1.0000005.toFixed(6)); // 1.000001 正確 console.log(1.00000005.toFixed(7)); // 1.0000000 錯(cuò)誤
首先,根據(jù)紅框的條件,x<10^21,1.0000005 與 1.00000005 都是小于 10^21 的,所以我們直接可以使用公式 n / 10^ - x 來玩耍。
我們先用 x=1.0000005 代入公式來看看情況:
// 假設(shè)n1 var n1 = 1000000; var x = 1.0000005; var f = 6; console.log((n1 / Math.pow(10, f) - x)); // -5.00000000069889e-7 // 假設(shè)n2 var n2 = 1000001; var x = 1.0000005; var f = 6; console.log((n2 / Math.pow(10, f) - x)); // 4.999999998478444e-7
由結(jié)果可知,當(dāng) n1=1000001 時(shí),得到的結(jié)果取最接近 0 的值,故:
console.log(1.0000005.toFixed(6)); // 1.000001 正確
再來試試當(dāng) x=1.00000005 代入公式:
// 假設(shè)n1 var n1 = 10000000; var x = 1.00000005; var f = 7; console.log((n1 / Math.pow(10,f) - x)); // -4.9999999918171056e-8 // 假設(shè)n2 var n2 = 10000001; var x = 1.00000005; var f = 7; console.log((n2 / Math.pow(10,f) - x)); // 5.000000014021566e-8
由結(jié)果可知,當(dāng) n2=10000001 時(shí),得到的結(jié)果取最接近 0 的值,故:
console.log(1.00000005.toFixed(7)); // 1.0000000 錯(cuò)誤
哎...寫到這里才發(fā)現(xiàn)給自己挖了一個(gè)大坑,干嘛要搞那么多零,自己都數(shù)暈圈了。。。
總的來說,上面例子就是教你如何通過規(guī)范定義的公式計(jì)算出結(jié)果而已,如果你看得懂規(guī)范,那么直接去代入也是沒有問題的。
總結(jié)
到此這篇關(guān)于JS中toFixed()方法四舍五入精度問題的文章就介紹到這了,更多相關(guān)JS toFixed()四舍五入精度問題內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Textarea輸入字?jǐn)?shù)限制實(shí)例(兼容iOS&安卓)
下面小編就為大家?guī)硪黄猅extarea輸入字?jǐn)?shù)限制實(shí)例(兼容iOS&安卓)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-07-07

JS實(shí)現(xiàn)對(duì)json對(duì)象排序并刪除id相同項(xiàng)功能示例

關(guān)于ES6新特性最常用的知識(shí)點(diǎn)匯總

通過javascript實(shí)現(xiàn)段落的收縮與展開