別被 0.1 + 0.2 給耍了,Javascript 的浮點數運算失真問題

更新於 發佈於 閱讀時間約 7 分鐘


「在 JavaScript 中 0.1 + 02 等於多少?」

這是我在面試時會問的一題。有經驗的工程師應該知道我在問什麼,但相信仍有不少人可能還不知道 0.1 + 0.2 不等於 0.3
幾年前有位同事跑來找我求救,他維護的某個早期專案出現線上問題,投資人在網頁上看到的金額與實際相差 1 塊。在聽完他的描述後,我問他這數字是前端計算的嗎?他回答是,此時直覺告訴我,這是浮點數精度問題。


浮點數精度問題?為什麼不等於 0.3?

簡單說,浮點數精度問題是指在使用 JavaScript 進行浮點數運算時可能出現的精度失真或精度丟失的情況。而造成浮點數精度問題的根本原因是電腦內部所使用的二進制浮點數表示法。
JavaScript 使用的是 IEEE 754 標準的雙精度浮點數表示法,即 64 位元,其中一位表示符號位,11 位表示指數,剩餘的 52 位表示尾數。
儘管這個表示法可以表示大範圍的數字,但它是基於二進制的,而我們通常使用的是十進制。這就導致了某些十進制小數無法完全精準地轉換為二進制表示,進而產生精度問題。例如,十進制中的 0.1 在該二進制中是一個無窮循環小數。這種問題在進行浮點數運算時尤為突出,因為即使是簡單的運算,結果也可能存在微小的誤差。

例如,在 JavaScript 中執行如下操作:

0.1 + 0.2 // 0.30000000000000004

根據我們的直覺,答案應該是 0.3。然而,在 JavaScript 中,由於浮點數精度問題,實際上結果可能是一個非常接近 0.3 的數字,但不是精確的 0.3

另一個造成精度問題的原因是浮點數的表示範圍限制。雖然雙精度浮點數可以表示非常大和非常小的數字,但在超出表示範圍時,將會出現溢出或下溢的情況,進而導致數字的失真。

在此就不對 IEEE 754 進行更詳細的說明,畢竟其浮點數行為與背後的原理已涉及計算機領域,相較之下前端工程師需更專注的是對浮點數的警惕與應對。如果有興趣了解原理,網上也有相當多資源。


凡遇浮點數,皆小心為上

在金融應用中,由於金額可能非常小且需要高精度計算,在進行複雜的計算時可能因此產生錯誤的判斷或造成四捨五入後的結果異常。
例如,計算某商品價格乘上某手續費後的最終價格,但因為浮點數精度問題,最終價格可能會有微小的偏差。以下列出幾個前端工程師可能遇到的情況。


1. 連續計算後的累積誤差:

在進行一連串浮點數的累積計算時,可能會累積誤差,導致最終結果與預期值不同。這在複雜的算法或迴圈迭代計算中尤其顯著。

let sum = 0.1
for (let i = 0; i < 10; i++) {
sum += 0.1
}

// 預期結果應為 1,但實際結果為 1.0999999999999999
console.log(sum)


2. 比較操作中的不確定性:​

在進行浮點數的比較操作時,可能會導致不符期望的比較或進入錯誤流程。

const foo = 0.1 + 0.2
const bar = 0.3

// 預期應該執行'小於等於'情境,但實際執行為'大於'情境
if (foo > bar) {
// 大於時執行某些動作...
console.log('大於');
} else {
// 小於等於時執行另外某些動作...
console.log('小於等於');
}


3. 不同計算順序的差異:

相同的數字在需求不變的情況下,即便只是計算順序不同,也可能因誤差而造成截然不同的結果。

const sum1 = 0.1 + 0.2 + 0.3 // 0.6000000000000001
const sum2 = 0.3 + 0.2 + 0.1 // 0.6
const isEqual = sum1 === sum2

console.log(isEqual) // 預期輸出 true,但實際為 false


不是你說不就不

既然前端計算浮點數是有風險的,相信一定會有人說,重要數字的計算本就不應該由前端處理,其實這算對也不算對。
金額、匯率、報酬率,手續費等等的數字在 domain 中的確是採後端計算結果為準並以此執行。但如果需即時呈現,假使都透過 API 取得,那就可能出現使用體驗上的不流暢,特別是當使用者就是變因之一時。

例如在基金交易中,手續費的玩法相當多樣,固定金額、單一費率、級距金額、級距費率、內含或外扣,適用基金標的或投資身份的優惠費率等等。
為了即時將結果呈現在畫面上,使用者於輸入框中每輸入一次金額就得透過 API 不斷重新查詢費率與計算手續費。
如果該數字不影響後續申購流程,那我們還可以讓這段邏輯以非同步方式執行。但如果後續流程中某些欄位受到該數字影響必須等待其結果呢?這時使用體驗上就會出現卡頓,即便你已經為輸入框做過防抖也一樣。
當你無法將使用者因素排除於變因之外,而使用者又能不間斷地改變因子時,這樣的體驗可能還沒等到上線,光是在 QA 就已經被提出了。

為此我們可能會這麼做,盡可能地在使用者未察覺的情況下透過 API 預先取得部分或全部計算邏輯。當需要計算時以落地方式立即處理,直接避開後端執行,API 響應時間與使用者網速等因素。當然這只是為了前台畫面的操作體驗與呈現,實際商業邏輯運算依然是後端處理。

工程師要懂得保護好自己

也因身處金融科技的產業特性,我會要求團隊內的工程師在做金額相關計算時一定要使用指定函式做特別處理。
千萬別小看那一點點的誤差,想像在電商購物被多扣一塊錢,你可能覺得沒什麼,大不了以後不用這家。但如果是定期定額交易時每次被多買一塊錢,你可能已經拿起手機撥打客訴專線了。不想被客訴甚至被主管機關盯上,那就別嫌麻煩,多做一步好好保護自己吧。


Cheng's murmur

來台北這麼多年,
最受不了的就是冬天那種要下不下的雨。
那傘撐也不是,不撐也不是。
留言
avatar-img
留言分享你的想法!
avatar-img
Cheng's
2會員
6內容數
生活就是 早上 8 點的文湖線;晚上 8 點的 New York Sour;帶著一台 GR3X 意興闌珊的漫步;嚮往著午後草皮上陪拉布拉多 🐶 玩耍;拿起似有似無的筆開始敲打創作。
你可能也想看
Thumbnail
介紹朋友新開的蝦皮選物店『10樓2選物店』,並分享方格子與蝦皮合作的分潤計畫,註冊流程簡單,0成本、無綁約,推薦給想增加收入的讀者。
Thumbnail
介紹朋友新開的蝦皮選物店『10樓2選物店』,並分享方格子與蝦皮合作的分潤計畫,註冊流程簡單,0成本、無綁約,推薦給想增加收入的讀者。
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
根據PHP官方說明,浮點數的精度有限。儘管取決於系統,PHP 通常使用 IEEE 754 雙精度格式,則由於取整而導致的最大相對誤差為 1.11e-16。非基本數學運算可能會給出更大誤差,並且要考慮到進行複合運算時的誤差傳遞。
Thumbnail
根據PHP官方說明,浮點數的精度有限。儘管取決於系統,PHP 通常使用 IEEE 754 雙精度格式,則由於取整而導致的最大相對誤差為 1.11e-16。非基本數學運算可能會給出更大誤差,並且要考慮到進行複合運算時的誤差傳遞。
Thumbnail
前言 這是紀錄本人學習Unity C#時的筆記,希望讓自己能夠整理思緒,方便記憶。 因為是新手自學的關係,也很有可能有誤解或錯誤的地方,請見諒… 變數 宣告變數不能使用數字開頭,或是除了_之外的符號。 ·整數 int ·浮點數 float、double float 精度低、佔的資源較少,double
Thumbnail
前言 這是紀錄本人學習Unity C#時的筆記,希望讓自己能夠整理思緒,方便記憶。 因為是新手自學的關係,也很有可能有誤解或錯誤的地方,請見諒… 變數 宣告變數不能使用數字開頭,或是除了_之外的符號。 ·整數 int ·浮點數 float、double float 精度低、佔的資源較少,double
Thumbnail
一、自動轉型-二、明確轉型-三、Parse方法-補充、日期時間的轉換>>> 當我們在撰寫程式過程中,有可能會遇到在做資料處理時與一開始宣告的資料型別不一樣,這時就會需要資料型別轉換了。那這邊有四種轉換介紹: 一、自動轉型 運算過程中在精確度不會改變時,程式就會自動幫我們做轉型處理,什麼意思呢?就是
Thumbnail
一、自動轉型-二、明確轉型-三、Parse方法-補充、日期時間的轉換>>> 當我們在撰寫程式過程中,有可能會遇到在做資料處理時與一開始宣告的資料型別不一樣,這時就會需要資料型別轉換了。那這邊有四種轉換介紹: 一、自動轉型 運算過程中在精確度不會改變時,程式就會自動幫我們做轉型處理,什麼意思呢?就是
Thumbnail
分類 △單精度浮點數、單精度浮點值(float) △雙精度浮點數、雙精度浮點值(double) △長雙精度浮點數、長雙精度浮點值(long double) 有效位數是什麼? 儲存形式 不精確的原因 範圍與有效位數的差別 浮點數不被建議使用的原因 精確問題 速度問題 結論
Thumbnail
分類 △單精度浮點數、單精度浮點值(float) △雙精度浮點數、雙精度浮點值(double) △長雙精度浮點數、長雙精度浮點值(long double) 有效位數是什麼? 儲存形式 不精確的原因 範圍與有效位數的差別 浮點數不被建議使用的原因 精確問題 速度問題 結論
Thumbnail
說明 重點 △定義變數 △文字的定義 △文字與數字的差別 △整數與浮點數 △signed(有號)與unsigned(無號)的區別 △e是什麼符號? 分類 △字元 △字串 △短整數 △整數 △長整數 △超長整數 △單精度浮點數 △雙精度浮點數 △長雙精度浮點數 應用 宣告與輸出 運算符 結論
Thumbnail
說明 重點 △定義變數 △文字的定義 △文字與數字的差別 △整數與浮點數 △signed(有號)與unsigned(無號)的區別 △e是什麼符號? 分類 △字元 △字串 △短整數 △整數 △長整數 △超長整數 △單精度浮點數 △雙精度浮點數 △長雙精度浮點數 應用 宣告與輸出 運算符 結論
Thumbnail
何謂浮點數? 在了解浮點數之前,可以先大概了解一下跟他相對應的定點數 定點數: 小數點固定,整個數字表示為 整數 + 小數 今天要表示 25.125 那定點數表示法就是 25 125,中間再以小數點作為連接 浮點數: 小數點是漂浮不定的,整個數字表示分為 有效數字跟指數,類似於科學記號表示法 今天要
Thumbnail
何謂浮點數? 在了解浮點數之前,可以先大概了解一下跟他相對應的定點數 定點數: 小數點固定,整個數字表示為 整數 + 小數 今天要表示 25.125 那定點數表示法就是 25 125,中間再以小數點作為連接 浮點數: 小數點是漂浮不定的,整個數字表示分為 有效數字跟指數,類似於科學記號表示法 今天要
Thumbnail
C#學習十天出頭,還在基礎的程式用語打混著。         常問那位工程師好友一堆蠢問題。某次看著書本中int整數相關程式碼的問題,當然問朋友,他也一頭霧水,因為我根本搞懂問題在哪?問的又是什麼?所以朋友也不知從何回答我起,我也翻翻書找找,也看到int的定義範圍,但還是「霧煞煞」,但是先把程式碼弄
Thumbnail
C#學習十天出頭,還在基礎的程式用語打混著。         常問那位工程師好友一堆蠢問題。某次看著書本中int整數相關程式碼的問題,當然問朋友,他也一頭霧水,因為我根本搞懂問題在哪?問的又是什麼?所以朋友也不知從何回答我起,我也翻翻書找找,也看到int的定義範圍,但還是「霧煞煞」,但是先把程式碼弄
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News