前情
最近在做一個後端需求,需求中需要前端做一些金額數字計算,前端對於小數的計算一直都有精度問題,如0.1+0.2計算的結果並不是0.3,而是0.30000000000000004,於是引入高精度計算庫math.js來解決前端計算的精度問題。
坑位
這次做的需求是一個退貨扣款的需求,如果使用者退回來的商品有磨損或者破壞需要進行扣除押金,但是扣除的金額不能超過押金,如果超過押金那就只能扣除押金的金額,開發中功能一切正常,測試也正常,但是一發布到線上發現無法正常工作,對於押金100的商品我想扣除50,正常來說扣款金額應該是50,但是實際顯示扣款100,導致提交時和服務端計算的值進行校驗不透過,以致始終無法完成資料提交,測試和我又立馬回到測試服自測又是一切正常。
Why?
使用math.js轉換或者計算方法計算完的值並不是數字,而是特有的BigNumber物件,當你直接拿物件比較的時候,它會呼叫物件的valueOf方法轉換為字串做比較,而字串比較是按位比較大小的,所以‘50’跟’100’比,因為5>1所以’50’大於’100’,關鍵程式碼如下,為了好理解其中變數被替換成中文:
...
// 實際扣款金額 違約金+磨損扣款金額,不能大於押金
const actualDeductionAmount = computed(() => {
let total = add(
bignumber(違約金 || 0),
bignumber(扣款金額 || 0)
);
let deposit = bignumber(押金金額 || 0);
if (total > deposit) {
total = deposit;
}
return format(total, { precision: 2, notation: "fixed" });
});
...
為什麼測試服是好的了,是因為這種扣款是不可退回的,測一次自己要損失一筆錢,於是測試服配置的商品都是小於1的金額的,首位大小都是0,導致比較結果都是正確的,也就沒有測試出來。
簡單demo測試地址:JS Bin - Collaborative JavaScript Debugging
解決方案
在做比較時使用math.js提供的轉數字方法number進行轉換後再執行比較,保證數字與數字的比較就不會有問題。
...
// 實際扣款金額 違約金+磨損扣款金額,不能大於押金
const actualDeductionAmount = computed(() => {
let total = add(
bignumber(違約金 || 0),
bignumber(扣款金額 || 0)
);
let deposit = bignumber(押金金額 || 0);
if (number(total) > number(deposit)) {
total = deposit;
}
return format(total, { precision: 2, notation: "fixed" });
});
...
思考
使用第三方工具庫時要細看官方文擋,並確定你按收到的返回值是什麼型別,此次使用前確實有瀏覽官方文擋的,但是個人英文真的很一般,不得不百度搜了一些使用教程,那些簡易使用教程並沒有明確說明返回的值型別,最近也在偶爾刷英語,希望能提高一些英語,對於程式設計師會英語還是很有幫助的,同時對於自測的時候應該多考慮一些邊界值,此次測試都只測了小數金額而同有測整數,也導致問題一直拖到生產環境,幸好的是沒有導致線上什麼經濟損失,不然就尷尬了。