遇到一個Bug
需求:在頁面上顯示一個產品的已購百分比
已購百分比 = ((總量 - 剩余數量)/總量) * 100%
顯示結果向下取整(取兩位有效數字)画切,
例如60.1% 則取60,60.9%也取60%
然而當總額=4000 剩余數量=1680時毫别,結果應該是正正好58%,
但程序運行時向下取整后結果卻是57%台丛,
我:砾肺??侠坎?
經過排查服務端取到的數據沒有問題硅蹦,上下文代碼也沒問題闷煤,那么問題出在哪兒呢鲤拿。
還不是因為我用了float類型
在說這個bug之前我們來復習一下幾個基礎知識
- 帶小數位的十進制數如何轉為二進制數
- float / double 類型是如何存儲的
關于第一點大部分程序員都已經會了署咽,簡單的說就是小數部分乘2取整,不會的同學請自行百度一下窒升,不占用篇幅饱须,重點是小數部分換算二進制時台谊,時常發(fā)生無限循環(huán),例如0.1酪呻,大家可以自己嘗試計算一下
關于float和double的存儲方式盐须,以float舉例(咱們挑和本篇問題有關的內容說
float類型即浮點型,一個float占4個字節(jié)
4個字節(jié)一共32位
從左往右第1位表示這個數的正負(a)
再往右的8個數位用于表示該浮點數存儲的指數部分(b)
最右邊的23位表示這個數字的有效數位(c)
正負表示位(1位) + 指數部分(8位) + 底數部分(23位) = 32位
abbbbbbb / bccccccc / cccccccc / cccccccc
然而23個底數位是不可能完整表示無限循環(huán)小數的
精度不夠闷尿,沒東西湊
一個無限循環(huán)眼溶,一個只有23位底數,那么結果是什么灌旧?
精度丟失绰筛,23位之后的數字無法表示铝噩,被當作0處理了
看回到我的bug上
當總額=4000 剩余數量=1680時,結果應該是正正好58%毛甲,
但程序運行時向下取整后結果卻是57%
經過計算
(4000 - 1680) / 4000 = 0.58
而0.58轉為2進制是無限循環(huán)的小數
float類型取了23個有效數位后具被,后面的數位給扔了……
實際上計算的結果為 0.5799999999.....
向下取整為 0.57
0.57 * 100% = 57%
原因就是如此,所以說啊七咧,基本功是很重要的艾栋,
那咋辦呢蛉顽?
1.最簡單的方法,不出現小數
在這個bug里遥诉,最簡單的修復方式是
(4000 - 1680) / 4000 * 100
改為
(4000 - 1680) * 100 / 4000
即將小數化為百分數的乘以100提前噪叙,使得小數不出現睁蕾,就不會存在無限循環(huán)數了(事實證明這樣確實有效)
但這只僅限于使用百分比的情況债朵,如果是一個大額度交易的精確計算序芦,需要用以下第2點的方法
2.有的時候出現小數是不可避免的粤咪,而同時需要非常高的精度,不容誤差(金錢宪塔、折扣方面的計算)囊拜;
那么這個時候,就要掏出我們針對float/double類型的計算工具了 —— NSDecimalNumber
NSDecimalNumber是基于十進制的定點計算冠跷,所以不會產生精度誤差
一個定點數包含了:用一個尾數(Mantissa)蜜托、一個基數(Base)、一個指數(Exponent)以及一個表示正負的符號(sign).
比如 15.99 用十進制科學計數法可以表達為 +1599 × 10?2 怠苔,其中 1.2345 為尾數仪糖,10 為基數迫肖,2 為指數。sign為 ‘+’故爵。
具體的使用請查閱文檔隅津,本文僅做一個提醒,歡迎批評指正探討
附帶一個JAVA的小函數结窘,用于將數字展開為double類型的存儲格式
(寫這篇博文的時候找不到這段代碼的出處了充蓝,看到出處的朋友請告訴我一下我附上鏈接)
public class showDouble {
public static void main(String[] args) {
printBits(3.54);
}
private static void printBits(double d) {
System.out.println("##"+d);
long l = Double.doubleToLongBits(d);
String bits = Long.toBinaryString(l);
int len = bits.length();
System.out.println(bits+"#"+len);
if(len == 64) {
System.out.println("[63]"+bits.charAt(0));
System.out.println("[62-52]"+bits.substring(1,12));
System.out.println("[51-0]"+bits.substring(12, 64));
} else {
System.out.println("[63]0");
System.out.println("[62-52]"+ pad(bits.substring(0, len - 52)));
System.out.println("[51-0]"+bits.substring(len-52, len));
}
}
private static String pad(String exp) {
int len = exp.length();
if(len == 11) {
return exp;
} else {
StringBuilder sb = new StringBuilder();
for (int i = 11-len; i > 0; i--) {
sb.append("0");
}
sb.append(exp);
return sb.toString();
}
}
}