19年目標:消滅英語!我新開了一個公眾號記錄一個程式設計師學英語的歷程
有提升英語訴求的小夥伴可以關注公眾號:csenglish 程式設計師學英語,每天花10分鐘交作業,跟我一起學英語吧
近期在專案中有出現大數值的訂單號
9148368244236619
在呼叫介面時自動變成9148368244236620
的情況,導致請求失誤。本文特意總結了出現這種情況的原因,以及js精度相關的情況。
jquery[.data()]方法
在本次案例中,訂單號是後端同步渲染到頁面上的,
<div class="j_OrderNumber" data-ordernumber="9148368244236619"></div>
複製程式碼
呈現在頁面上的訂單號數值沒有問題9148368244236619
,前端此時想獲取到這個訂單號:
var orderNumber = $(`.j_OrderNumber).data(`ordernumber`); // 9148368244236620
typeof(orderNumber) === `number` // true
複製程式碼
在這個取值賦值的過程中,超過安全值的大數值發生了變化,從9148368244236619
變成了91483682442366120
,這裡的問題主要出現在jquery
的data
方法。
超過安全值Math.pow(2, 53)-1 -- 9007199254740991
的number型別在賦值的過程中會發生精度丟失,而我們在使用jquery的data方法取得的自定義html屬性data-ordernumber
值會強制轉換成number型別,導致精度丟失。
而使用js原生的方法取dom節點的屬性時,獲取到的值都是string型別,這樣就不會出現number型別精度丟失的問題。
var orderNumber = $(`.j_OrderNumber`)[0].dataset.ordernumber // `9148368244236619`
typeof(orderNumber) === `string` // true
複製程式碼
所以我們在通過自定義屬性取值number型別時,並且預期這些值會是類似訂單號這種會超過安全閾值的數值時,不要使用jquery的data方法。
大數值的精度問題
能夠被“安全”呈現的最大整數是Math.pow(2, 53) - 1
,即9007199254740992
。在ES6中被定義為Number.MAX_SAFE_INTEGER
。
在開發環境,根據程式的特殊性,在有可能出現這種情況時我們應該杜絕掉超出安全閾值的大整數,並給出友好提示:
function isSafeInteger(num) {
return typeof(num) === `number` && num % 1 == 0 && Math.abs(num) <= Math.pow(2, 53) -1;
}
複製程式碼
在一般電商業務中比較常見出現大數值的場景也就是訂單號了,這類場景後端傳值給我們的時候都強制包裝成string型別就會解決大多數的精度丟失問題了。
至於為什麼會只有2的53次方-1的整數是安全的,可以看阮神的關於數值的文章有詳細介紹
小數的精度問題
經典的 0.1 + 0.2 === 0.3 // false 問題
0.1 + 0.2 === 0.30000000000000004
小數比較
對於這類數值比較問題,如果我們已經知道了目標比較值,即如果我們已經明確要與0.3
進行比較,我們也可以不需要得到0.1+0.2的真實期望結果值(0.3),因為如果我們要得到0.3,還需要對0.1和0.2進行操作。常規解法:
(0.1 * 10 + 0.2 * 10) / 10 = 0.3
複製程式碼
在「你不知道的javascript」一書中有提到一種判斷方法,設定一個誤差範圍值,通常也稱為“機器精度”,對於javascript來說,這個值通常為Math.pow(2, -53)
。
我們可以將需要比較的兩個值進行相減,再與這個機器精度進行比較,如果在誤差範圍內,我們也視為兩個值是相等的。
function numbersCloseEnoughToEqual(num1, num2) {
return Math.abs(num1 - num2) < Math.pow(2, -53);
}
複製程式碼
小數展示
對於電商業務來講,小數經過四則運算後可能會出現失去精度的問題,但是作為展示來說我們都會呼叫toFixed()
進行小數後幾位的約定,呼叫了這個方法後小數失去精度的問題也就迎刃而解了,不可能出現0.30000000000000004
這樣的數值。
所以在業務中有需要進行小數四則運算並會展示在頁面中,呼叫toFixed()
方法!
但是toFixed()
也有失去精度的時候!
1.335.toFixed(2)
// "1.33"
複製程式碼
function toFixed(num, s) {
var times = Math.pow(10, s)
var des = num * times + 0.5
des = parseInt(des, 10) / times
return des + ``
}
複製程式碼
小數的四則運算
本文來自二口南洋,有什麼需要討論的歡迎找我。