跳至主要内容

JavaScript - 浮點數計算之 0.1 + 0.2 !== 0.3

0.1 + 0.2 等於 ?

0.30000000000000004



因此 0.1 + 0.2 === 0.3false

為什麼會不精準 ?

JavaScript 的浮點數計算採用 IEEE 二進位浮點數算術標準 ( IEEE 754 ) 中的雙精確度 ( 64 位元 ) 浮點數。

信息

( 維基百科 - IEEE 754 ) IEEE 二進位浮點數算術標準(IEEE 754)是 20 世紀 80 年代以來最廣泛使用的浮點數運算標準,標準中定義了表示浮點數的格式,包含負零、反常值 ( denormal number )、特殊數值 ( 無窮值 Inf、非數值 NaN ),以及這些數值的「浮點數運算子」;它也指明了四種數值修約規則和五種例外狀況(包括例外發生的時機與處理方式)。

0.10.2 在轉換為二進位實際儲存的值再轉換為十進位後會比原先略大 ( 這裡就已經損失精度 ),在二進位運算後得到的結果就會是 0.30000000000000004

但有時候在 JavaScript 運算又會得到理想的數值,例如 0.5 + 0.625 會精準得到 1.125,原因是二進位能精確表示「位數有限且分母為 2 的倍數的小數」,因此 0.50.250.1250.625 等都可以被轉換為精確的二進位儲存。

不過,運算小數明明不是位數有限且分母為 2 的倍數的小數,卻有可能獲得正確的計算結果,例如 0.1 + 0.3 會正確得到 0.4,由於 IEEE 754 規則會將浮點數截斷,所以是「有可能」會在浮點數計算中得到預期結果。

提示

所以在判斷式中盡量不要直接比較兩個浮點數,很容易遇到非預期結果。

如何修正 ?

JS 原生方法

.toFixed

(0.1 + 0.2).toFixed(1) // '0.3'
(0.2 + 0.4).toFixed(1) // '0.6'

.toPrecision

(0.1 + 0.2).toPrecision(1) // '0.3'
(0.2 + 0.4).toPrecision(1) // '0.6'

但使用以上兩種方法仍然不是最安全的,可以看以下例子 :

0.15.toFixed(1) // '0.1'
0.15.toPrecision(1) // '0.1'

0.25.toFixed(1) // '0.3'
0.25.toPrecision(1) // '0.3'

當想要做四捨五入時還是會因為浮點數精確度問題造成非預期結果。

第三方套件

  • Math.js
    math.format(0.1 + 0.2, {precision: 14}) // '0.3'
    math.equal(0.1 + 0.2, 0.3) // true
    math.format(0.15, { precision: 1 }) // '0.2'
    math.format(0.25, { precision: 1 }) // '0.3'
  • decimal.js
    let x = new Decimal(0.1)
    let ans = x.add(0.2)

    ans.toString() // '0.3'
    ans.equals(0.3) // true
    let a = new Decimal(0.15)
    let b = new Decimal(0.25)

    a.toFixed(1) // '0.2'
    a.toPrecision(1) // '0.2'
    b.toFixed(1) // '0.3'
    b.toPrecision(1) // '0.3'
  • bignumber.js
    let x = new BigNumber(0.1)
    let ans = x.plus(0.2)

    ans.toString() // '0.3'
    ans.isEqualTo(0.3) // true
    let a = new BigNumber(0.15)
    let b = new BigNumber(0.25)

    a.toFixed(1) // '0.2'
    a.toPrecision(1) // '0.2'
    b.toFixed(1) // '0.3'
    b.toPrecision(1) // '0.3'

Reference