JAVA浮點數計算精度損失底層原理與處理方案。本站提示廣大學習愛好者:(JAVA浮點數計算精度損失底層原理與處理方案)文章只能為提供參考,不一定能成為您想要的結果。以下是JAVA浮點數計算精度損失底層原理與處理方案正文
浮點數會有精度損失這個在上大學的時分就曾經原告知,但是至今完全沒有想明白其中的原由,教師講的時分也是一筆帶過的,自己也沒有好好揣摩。終於在任務的時分碰到了,於是google了一番。
問題:
對兩個double類型的值停止運算,有時會呈現後果值異常的問題。比方:
1 System.out.println(19.99+20); 2 System.out.println(1.0-0.66); 3 System.out.println(0.033*100); 4 System.out.println(12.3/100);
輸入:
39.989999999999995
0.33999999999999997
3.3000000000000003
0.12300000000000001
Java中的復雜浮點數類型float和double不可以准確運算。這個問題其實不是JAVA的bug,由於計算機自身是二進制的,而浮點數實踐上只是個近似值,所以從二進制轉化為十進制浮點數時,精度容易喪失,招致精度下降。
關於精度損失的原理可以很復雜的講,首先一個正整數在計算機中表示運用01010方式表示的,浮點數也不例外。
比方11,11除以2等於5余1
5除以2等於2余1
2除以2等於1余0
1除以2等於0余1
所以11二進制表示為:1011.
double類型占8個字節,64位,第1位為符號位,前面11位是指數局部,剩余局部是無效數字。
正整數除以2一定會有個止境的,之後二進制復原成十進制只需求乘以2即可。
舉個例子:0.99用的無效數字局部,
0.99 * 2 = 1+0.98 --> 1
0.98 * 2 = 1+0.96 --> 1
0.96 * 2 = 1+0.92 -- >1
0.92 * 2 = 1+0.84 -- >1
...............
這樣循環往復是沒法有止境的,而double無效數字無限,所以肯定會有損失,所以二進制無法精確表示0.99,好像十進制無法精確表示1/3一樣。
處理方法:
在《Effective Java》中提到一個准繩,那就是float和double只能用來作迷信計算或許是工程計算,但在商業計算中我們要用java.math.BigDecimal,經過運用BigDecimal類可以處理上述問題,首先需求留意的是,直接運用字符串來結構BigDecimal是相對沒有精度損失的,假如用double或許把double轉化成string來結構BigDecimal仍然會有精度損失,所以我覺得這種處理辦法就是在運用中就把浮點數用string來表示寄存,觸及到運算直接用string結構double,否則一定會有精度損失。
1. 相加
1 /** 2 * 相加 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double add(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.add(b2).doubleValue(); 11 }
2. 相減
1 /** 2 * 相減 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double sub(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.subtract(b2).doubleValue(); 11 }
3. 相乘
1 /** 2 * 相乘 3 * @param double1 4 * @param double2 5 * @return 6 */ 7 public static double mul(String doubleValA, String doubleValB) { 8 BigDecimal a2 = new BigDecimal(doubleValA); 9 BigDecimal b2 = new BigDecimal(doubleValB); 10 return a2.multiply(b2).doubleValue(); 11 }
4. 相除
1 /** 2 * 相除 3 * @param double1 4 * @param double2 5 * @param scale 除不盡時指定精度 6 * @return 7 */ 8 public static double div(String doubleValA, String doubleValB, int scale) { 9 BigDecimal a2 = new BigDecimal(doubleValA); 10 BigDecimal b2 = new BigDecimal(doubleValB); 11 return a2.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue(); 12 }
5. 主函數調用
1 public static void main(String[] args) { 2 String doubleValA = "3.14159267"; 3 String doubleValB = "2.358"; 4 System.out.println("add:" + add(doubleValA, doubleValB)); 5 System.out.println("sub:" + sub(doubleValA, doubleValB)); 6 System.out.println("mul:" + mul(doubleValA, doubleValB)); 7 System.out.println("div:" + div(doubleValA, doubleValB, 8)); 8 }
後果展現如下所示:
add:5.49959267
sub:0.78359267
mul:7.40787551586
div:1.33231241
所以最好的辦法是完全丟棄double,用string和java.math.BigDecimal。
java遵照IEEE制定的浮點數表示法來停止float,double運算。這種構造是一種迷信計數法,用符號、指數和尾數來表示,底數定為2——即把一個浮點數表示為尾數乘以2的指數次方再添上符號。詳細底層如何存儲以及如何停止運轉請持續關注我的博客,後續我會將概況總結好的。