本文最初發表於David Kaye博客
我在所有嘗試過的JavaScript環境(Chrome、Firefox、Internet Explorer、Brave和Node.js)中都發現了Number().toFixed()中的捨入錯誤。令人驚訝的是,修復方法非常簡單。繼續閱讀……
在修改一個執行與Intl.NumberFormat#format()相同功能的數字格式化函數時,我發現了toFixed()中此版本的捨入錯誤。
(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"
失敗的測試位於此處第42行。直到2017年12月我才發現它,這促使我去檢查其他問題。
請參閱我關於此問題的推文:
關於使用toFixed()的捨入錯誤,有很長的錯誤報告歷史。
以下是一些關於此問題的StackOverflow問題的簡短示例:
通常,這些都指出了一個值的一個錯誤,但沒有報告返回錯誤結果的值的範圍或模式(至少我沒有找到,我可能錯過了什麼)。這使得程序員只能關注小問題而看不到更大的模式。我不責怪他們這樣。
基於輸入的意外結果必須源於輸入中的共享模式。因此,我沒有審查Number().toFixed()的規範,而是專注於使用一系列值進行測試,以確定錯誤在每個系列中的出現位置。
我創建了以下測試函數,以對一系列從1到maxValue的整數進行toFixed()運算,為每個整數添加例如.005的小數。 toFixed()的固定(數字位數)參數根據小數值的長度計算。
(1.015).toFixed(2) // 返回 "1.01" 而不是 "1.02"
以下示例對整數1到128執行操作,為每個整數添加小數.015,並返回一個“意外”結果數組。每個結果包含given、expected和actual字段。在這裡,我們使用該數組並打印每個項目。
function test({fraction, maxValue}) { fraction = fraction.toString() var fixLength = fraction.split('.')[1].length - 1 var last = Number(fraction.charAt(fraction.length - 1)) var fixDigit = Number(fraction.charAt(fraction.length - 2)) last >= 5 && (fixDigit = fixDigit + 1) var expectedFraction = fraction.replace(/[\d]{2,2}$/, fixDigit) return Array(maxValue).fill(0) .map(function(ignoreValue, index) { return index + 1 }) .filter(function(integer) { var number = integer + Number(fraction) var actual = number.toFixed(fixLength) var expected = Number(number + '1').toFixed(fixLength) return expected != actual }) .map(function(integer) { var number = Number(integer) + Number(fraction) return { given: number.toString(), expected: (Number(integer.toFixed(0)) + Number(expectedFraction)).toString(), actual: number.toFixed(fixLength) } }) }
對於這種情況,有6個意外結果。
test({ fraction: .015, maxValue: 128 }) .forEach(function(item) { console.log(item) })
我發現此錯誤包含三個部分:
例如,對於整數1到128,(value).toFixed(2)使用以5結尾的不同3位小數,產生以下結果:
那些比我更了解二進制和浮點數學知識的人可能能夠推斷出根本原因。我把這留給讀者作為練習。
通過多於一位小數來修復值總是正確舍入;例如,(1.0151).toFixed(2)按預期返回“1.02”。測試和polyfill都使用該知識進行正確性檢查。
這意味著所有toFixed()實現都有一個簡單的修復方法:如果值包含小數,則在要修改的值的字符串版本末尾附加“1”。這可能不是“符合規範的”,但這意味著我們將獲得預期的結果,而無需重新審視較低級別的二進製或浮點運算。
在所有實現都被修改之前,如果您願意這樣做(並非每個人都願意),您可以使用以下polyfill覆蓋toFixed()。
<code>Object { given: "1.015", expected: "1.02", actual: "1.01" } Object { given: "4.015", expected: "4.02", actual: "4.01" } Object { given: "5.015", expected: "5.02", actual: "5.01" } Object { given: "6.015", expected: "6.02", actual: "6.01" } Object { given: "7.015", expected: "7.02", actual: "7.01" } Object { given: "128.015", expected: "128.02", actual: "128.01" }</code>
然後再次運行測試並檢查結果長度是否為零。
(1.005).toFixed(2) == "1.01" || (function(prototype) { var toFixed = prototype.toFixed prototype.toFixed = function(fractionDigits) { var split = this.toString().split('.') var number = +(!split[1] ? split[0] : split.join('.') + '1') return toFixed.call(number, fractionDigits) } }(Number.prototype));
或者只運行啟動這篇文章的初始轉換。
test({ fraction: .0015, maxValue: 516 }) // Array [] test({ fraction: .0015, maxValue: 516 }).length // 0
感謝您的閱讀!
JavaScript中的Number.toFixed()方法用於使用定點表示法格式化數字。它返回數字的字符串表示形式,該表示形式不使用指數表示法,並且小數點後恰好有指定數量的位數。如有必要,將對數字進行舍入,並且結果字符串的小數點後長度特定。
由於JavaScript處理二進制浮點數的方式,Number.toFixed()方法有時會給出不准確的結果。 JavaScript使用二進制浮點數,它無法準確表示所有十進制小數。當使用Number.toFixed()方法將數字舍入到特定的小數位數時,這種不准確性會導致意外結果。
修復Number.toFixed()方法中的捨入錯誤的一種方法是使用自定義舍入函數。此函數可以考慮JavaScript的數字處理的特性並提供更準確的結果。例如,您可以使用一個函數,該函數將數字乘以10的冪,將其舍入到最接近的整數,然後將其除以相同的10的冪。
不可以,Number.toFixed()方法只能與數值一起使用。如果您嘗試將其與非數值一起使用,JavaScript將拋出TypeError。如果您需要將此方法與可能不是數字的值一起使用,則應首先檢查該值是否為數字。
Number.toFixed()方法與其他舍入方法之間的性能差異通常可以忽略不計。但是,如果您正在執行大量操作,則使用自定義舍入函數可能會比使用Number.toFixed()方法略快。
不可以,Number.toFixed()方法只能捨入到最多20位小數。如果您嘗試舍入到超過20位小數,JavaScript將拋出RangeError。
Number.toFixed()方法處理負數的方式與處理正數的方式相同。它將數字的絕對值舍入到指定的小數位數,然後添加負號。
是的,Number.toFixed()方法是JavaScript的標準部分,應該在所有JavaScript環境中可用。但是,由於不同JavaScript引擎處理數字的方式不同,因此結果在所有環境中可能並不完全相同。
如果您不向Number.toFixed()方法傳遞任何參數,它將默認舍入到0位小數。這意味著它將把數字舍入到最接近的整數。
是的,您可以將Number.toFixed()方法與大數一起使用。但是,請記住,JavaScript只能準確表示最大為2^53 – 1的數字。如果您嘗試將Number.toFixed()方法與大於此數字的數字一起使用,則它可能不會給出準確的結果。
以上是number()。tofixed()舍入錯誤:破裂但可固定的詳細內容。更多資訊請關注PHP中文網其他相關文章!