大家都知道,在 JavaScript 裡,0.1 + 0.2 !== 0.3
,因為浮點數並不是精確值。
那麼需要比較小數的時候怎麼辦呢?只要定義好誤差範圍即可,依據 IEEE 754 的標準,64 位元的雙精度浮點數(double)的誤差值應在 2^-52 以內(JavaScript 浮點數:符號佔 1 位,指數佔 11 位,小數佔 52 位,加起來共 64 位),因此,我們可以依據這個標準定義出精確度,就可以進行比較了,以下為實作範例:
const EPSILON = Math.pow(2, -52); // 2.220446049250313e-16
const epsEqu = (x, y) => Math.abs(x - y) < EPSILON;
// usage
epsEqu(0.1 + 0.2, 0.3); // true
20180203 更新:
Number.EPSILON
在 ES6 之後, JS 已經有定義一些 Number
的常數(spec 在這),其中就有 Number.EPSILON
,因此上面的 function 我們可以這樣用:
const epsEqu = (x, y) => Math.abs(x - y) < Number.EPSILON;
特別注意 IE 不支援,需要 polyfill:
if (Number.EPSILON === undefined) Number.EPSILON = Math.pow(2, -52);
以為這樣就天下太平了嗎?不!JS 總是充滿驚喜,這樣會出問題,至少我用 chrome 實測會有問題:
epsEqu(32.17 - 0.01, 32.16); // false
這邊本來應該輸出為 true
的,但是在某些數字卻會輸出 false
……
雖然覺得是瀏覽器實作的問題,但這逼得我們不得不想別的辦法
B 計畫:Math.round()
workaround 雖然可恥但是有用,這裡的邏輯是先將數字變成整數再來計算,避免浮點數取值問題,但就需要依照需求修改誤差範圍了。範例如下:
// eplilon: 算到小數第幾位
const epsEqu = (x, y, epsilon = 4) => {
return Math.round(x * Math.pow(10, epsilon)) === Math.round(y * Math.pow(10, epsilon));
}