javascript
ieee754浮点数转换工具_关于JS浮点数运算不精确的原因和解决方案
背景
之前在一個項目中,涉及到了金額,協議組定的標準是按照分的單位進行傳遞的,但是交互上,web頁面中為了更友好的體驗,是使用的元作為單位的,這個時候就需要轉換一下單位
本來是很簡單的一個轉化的需求,在和后端聯調的時候發現,保存的時候返回了參數錯誤,原因就是由于js浮點數精度帶來的影響,導致保存的時候保存的位數特別多。
之前的開發過程中,對這個不精確的問題只是了解,有問題了就parseInt一下,但沒有去細想過要怎么解決,所以今天整理了一下之后分享一下,先了解下原因,再看下怎么解決和規避
問題
輸入金額 0.55,我傳遞之后應該乘 100 后下發,正常來說應該傳的是55,但是實際上,由于精度丟失,最后的結果如下圖所示:
那追根溯源,到底為什么會產生精度丟失的問題呢?
計算機底層只有0 和 1, 所以所有的運算最后實際上都是二進制運算。十進制整數利用輾轉相除的方法可以準確地轉換為二進制數,但浮點數呢?
先看下面一張圖,是關于IEEE 754標準(IEEE二進位浮點數算術標準(IEEE Standard for Floating-Point Arithmetic)的標準編號):
這個標準是JS的浮點數的實現標準,大概解釋一下這張圖就是:
- 第一位是符號位
- 中間11位代表的是指數位
- 最后的52位代表尾數位
也就是說,浮點數最終在運算的時候實際上是一個符合該標準的二進制數
我們可以看一個例子:
為了驗證該例子,我們得先知道怎么將浮點數轉換為二進制,整數我們可以用除2取余的方式,小數我們則可以用乘2取整的方式。
0.1轉換為二進制:
- 0.1 * 2,值為0.2,小數部分0.2,整數部分0
- 0.2 * 2,值為0.4,小數部分0.4,整數部分0
- 0.4 * 2,值為0.8,小數部分0.8,整數部分0
- 0.8 * 2,值為1.6,小數部分0.6,整數部分1
- 0.6 * 2,值為1.2,小數部分0.2,整數部分1
- 0.2 * 2,值為0.4,小數部分0.4,整數部分0
- 從0.2開始循環
0.2轉換為二進制:
- 可以直接參考上述,肯定最后也是一個循環的情況
所以最終我們能得到兩個循環的二進制數:
0.1:0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1100 ...
0.2:0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 ...
這兩個的和的二進制就是:
sum:0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 ...
所以最終我們只能得到和的近似值(按照IEEE 754標準保留 52位,按 0舍1入 來取值)
利用按權相加法,sum的十進制數則為:
sum ≈ 0.30000000000000004
這個例子說明了什么?
說明JS浮點數精度的缺失實際上是因為浮點數的小數部分無法用二進制很精準的轉換出來,而以近似值來進行運算的話,肯定就存在精度的問題
解決
了解了真相之后,我們可以怎么處理呢?
我的項目中是直接parseInt了一下,因為誤差很小,可以忽略不計。
但后來想想不能這么就放過去了,這種一刀切的方法太暴力了,萬一涉及到需要特別精確的場景,這種方法會有問題。
所以,最好的辦法還是需要找方法提高精度,直接規避。
思路其實非常簡單,既然浮點數的情況下會丟失精度,那我們所有運算的時候都先小浮點數轉換為整數,等計算完之后,再按比例轉換會浮點數,這樣就避免了再二進制十進制轉換的時候計算機的精度問題。
比如上面的加法的例子,我們定義一個工具函數add:
const現在我們再來看看結果:
但有個問題,我們之前還有個例子,0.55在乘的時候就會產生精度問題,所以這里我們再優化一下,把一個數變成整數,是不是也可以理解為將其變成字符串后把小數點去掉再轉回數字,這樣效果和相乘的效果不是一樣的嗎:
const這樣就解決了。
當然,這只是考慮了最簡單的情況,比如如果位數不同還要特殊處理下。不過只要思路有了,后續的就屬于添磚加瓦了,考慮得很全面的話,可以考慮封裝成一個庫,這篇文章就權當拋磚引玉吧。
總結
以上是生活随笔為你收集整理的ieee754浮点数转换工具_关于JS浮点数运算不精确的原因和解决方案的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: aws php mysql,AWS快速搭
- 下一篇: springboot profile_S