bigdecimal 保留两位小数_一起聊聊小数的储存和运算
原創: 蜀中亮子 玄說前端
小數運算的問題
在 js 中的小數運算中,一直存在著一個問題,
比如:0.1+0.2=0.30000000000000004 、0.4-0.3=0.10000000000000003。
那么為什么會出現這種情況呢?這種情況又如何解決呢?
為什么?
這就要講到 js 小數的存儲,在 js 找那個所有的數字包括小數和整數都只有一種類型—Number。它實現遵循 IEEE 754 標準。使用 64 位固定長度來表示。
這樣的存儲結構優點是可以歸一化處理整數和小數,節省存儲空間。
64 位比特又可分為三個部分:
- 符號位 S:第 1 位是正負數符號位(sign),0 代表正數,1 代表負數
- 指數位 E:中間的 11 位存儲指數(exponent),用來表示次方數
- 尾數位 M:最后的 52 位是尾數(mantissa),超出的部分自動進一舍零
實際數字就可以用以下公式來計算:
注意以上的公式遵循科學計數法的規范,在十進制是為 0<M<10,到二進制就是 0<M<2。也就是說整數部分只能是 1,所以可以被舍去,只保留后面的小數部分(想不通這個道理的,可以看下下面0.1的例子)。
比如:十進制 4.5 轉換成二進制就是 100.1,科學計數法表示是 1.001*2^2,舍去 1 后 M = 001;
E 是一個無符號整數,因為長度是 11 位,取值范圍是 0~2047。但是科學計數法中的指數是可以為負數的,所以再減去一個中間數 1023,[0,1022]表示為負,[1024,2047] 表示為正。如 4.5 的指數 E = 1025,尾數 M 為 001。
最終的公式變成:
所以 4.5 最終表示為(S=1、E=1025、M=001);
再以 0.1 例解釋浮點誤差的原因, 0.1 轉成二進制表示為 0.0001100110011001100(1100 循環),1.100110011001100x2^-4,所以 E=-4+1023=1019;M 舍去首位的 1,得到 100110011...。轉化成十進制后為 0.100000000000000005551115123126,因此就出現了浮點誤差。
那小數是怎么轉為二進制的呢?
小數轉二進制
比如數字 3.2 轉為二進制的過程;
- 第一步:0.32*2=0.64,這個時候整數部分是 0,所以第一位是 0,那么就是 0.0
- 第二步:0.64*2=1.28,這個時候整數部分是 1,所以第二位是 1,那么就是 0.01
- 第三步:把取了一之后的 1.28 減去 1 之后轉換為 0.28,拿這個部分再乘以二,那么就是 0.28*2=0.56,這個時候整數部分是 0,所以第三位是 0,那么就成了 0.010
- 第四步:0.56*2=1.12,這個時候整數部分是 1,所以第四位是 1,那么就是 0.0101,
- 第五步:和第三步的思路一樣 這樣一直到最后小數部分沒有了的時候,就算是轉換完成。為什么要乘以二,因為使用二進制數據表示數的時候,只有兩位,0 和 1。
怎么解決這個問題
第一種
把小數轉成整數后再運算。以加法為例:
0.1+0.2
把數字都乘以他們可以轉換為整數的倍數,計算之后,再除以乘上的倍數。比如:(0.1*10+0.2*10)/10
第二種
還有另一個方案,是做一個自己的計算過程,比如 0.1+0.2,首先通過正則或者 AST 或者其他的解析方式,匹配出了符號和數字,再把字符按照四則運算法的規律來計算,0.1 和 0.2 取小數位后第一位相加減,是否進 1,發現不需要去整數位第一位加減,依次遞推。這個方法的思路就和我們豎式計算一樣,把豎式計算的思路,代碼化。至此,小數的計算與原因都找到了。拓展一下,在計算機底層 cpu 是怎么進行加減乘除運算的呢?可以進群討論最后附上想進前端群的可以加微信,備注:玄說前端。
END
獲得更多信息關注公眾號
總結
以上是生活随笔為你收集整理的bigdecimal 保留两位小数_一起聊聊小数的储存和运算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 知识图谱论文阅读【十二】【KDD2020
- 下一篇: Hive关于数据表的增删改(内部表、外部