隱藏精度
2016 年 11 月 22 日
有時我在處理資料時,資料的精度會比我預期的更高。有人可能會認為這是一件好事,畢竟精度越高越好,所以精度越高越好。但隱藏精度可能會導致一些難以察覺的錯誤。
const validityStart = new Date("2016-10-01"); // JavaScript const validityEnd = new Date("2016-11-08"); const isWithinValidity = aDate => (aDate >= validityStart && aDate <= validityEnd); const applicationTime = new Date("2016-11-08 08:00"); assert.notOk(isWithinValidity(applicationTime)); // NOT what I want
上述程式碼中發生的是,我打算透過指定開始和結束日期來建立一個包含的日期範圍。但是我實際上沒有指定日期,而是指定時間點,所以我沒有將結束日期標記為 11 月 8 日,而是將結束標記為 11 月 8 日的 00:00。因此,11 月 8 日內的任何時間(除了午夜)都不在預計包含在內的日期範圍內。
隱藏精度是日期常見的問題,因為遺憾的是,通常會有提供類似時間點的日期建立函式。這是命名不佳的範例,而且的確是日期和時間的普遍建模不佳。
日期是隱藏精度問題的良好範例,但另一個罪魁禍首是浮點數。
const tenCharges = [ 0.10, 0.10, 0.10, 0.10, 0.10, 0.10, 0.10, 0.10, 0.10, 0.10, ]; const discountThreshold = 1.00; const totalCharge = tenCharges.reduce((acc, each) => acc += each); assert.ok(totalCharge < discountThreshold); // NOT what I want
當我剛剛執行它時,記錄陳述顯示 totalCharge
為 0.9999999999999999
。這是因為浮點數無法精確表示許多值,導致在尷尬的時間會出現一點看不見的精度。
從中得出的結論之一是,你應該非常小心使用浮點數來表示金錢。(如果你有小數貨幣部分,例如美分,那麼通常最好在小數值上使用整數,用 500 表示 5.00 歐元,最好是在 金錢類型 中)更通用的結論是,浮點數在比較時很棘手(這就是測試架構斷言始終具有比較精度的緣故)。