聊聊Java Double相加出現的怪事

Java Double相加出現的怪事

問題的提出

編譯運行下面這個程序會看到什麼

public class test {
 public static void main(String args[]) {
  System.out.println(0.05 + 0.01);
  System.out.println(1.0 - 0.42);
  System.out.println(4.015 * 100);
  System.out.println(123.3 / 100);
 }
};

你沒有看錯!結果確實是

0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999

Java中的簡單浮點數類型float和double不能夠進行運算。不光是Java,在其它很多編程語言中也有這樣的問題。在大多數情況下,計算的結果是準確的,但是多試幾次(可以做一個循環)就可以試出類似上面的錯誤。現在終於理解為什麼要有BCD碼瞭。

這個問題相當嚴重,如果你有9.999999999999元,你的計算機是不會認為你可以購買10元的商品的。

在有的編程語言中提供瞭專門的貨幣類型來處理這種情況,但是Java沒有。現在讓我們看看如何解決這個問題。

解決方案

現在我們已經可以解決這個問題瞭,原則是使用BigDecimal並且一定要用String來夠造。

但是想像一下吧,如果我們要做一個加法運算,需要先將兩個浮點數轉為String,然後夠造成BigDecimal,在其中一個上調用add方法,傳入另一個作為參數,然後把運算的結果(BigDecimal)再轉換為浮點數。你能夠忍受這麼煩瑣的過程嗎?下面我們提供一個工具類Arith來簡化操作。它提供以下靜態方法,包括加減乘除和四舍五入:

public static double add(double v1, double v2); 
 public static double sub(double v1, double v2); 
 public static double mul(double v1, double v2); 
 public static double div(double v1, double v2); 
 public static double div(double v1, double v2, int scale); 
 public static double round(double v, int scale);
package org.nutz.mvc.core; 
import java.math.BigDecimal; 
public class Arith {
 // 源文件Arith.java:
 
 /**
  * 由於Java的簡單類型不能夠精確的對浮點數進行運算,這個工具類提供精 確的浮點數運算,包括加減乘除和四舍五入。
  */
 
 // 默認除法運算精度
 private static final int DEF_DIV_SCALE = 10;
 
 // 這個類不能實例化
 private Arith() {
 }
 
 /**
  * 提供精確的加法運算。
  * 
  * @param v1
  *            被加數
  * @param v2
  *            加數
  * @return 兩個參數的和
  */
 
 public static double add(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.add(b2).doubleValue();
 }
 
 /**
  * 提供精確的減法運算。
  * 
  * @param v1
  *            被減數
  * @param v2
  *            減數
  * @return 兩個參數的差
  */
 
 public static double sub(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.subtract(b2).doubleValue();
 }
 
 /**
  * 提供精確的乘法運算。
  * 
  * @param v1
  *            被乘數
  * @param v2
  *            乘數
  * @return 兩個參數的積
  */
 
 public static double mul(double v1, double v2) {
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.multiply(b2).doubleValue();
 }
 
 /**
  * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到 小數點以後10位,以後的數字四舍五入。
  * 
  * @param v1
  *            被除數
  * @param v2
  *            除數
  * @return 兩個參數的商
  */
 
 public static double div(double v1, double v2) {
  return div(v1, v2, DEF_DIV_SCALE);
 }
 
 /**
  * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 定精度,以後的數字四舍五入。
  * 
  * @param v1
  *            被除數
  * @param v2
  *            除數
  * @param scale
  *            表示表示需要精確到小數點以後幾位。
  * @return 兩個參數的商
  */
 
 public static double div(double v1, double v2, int scale) {
  if (scale < 0) {
   throw new IllegalArgumentException(
     "The   scale   must   be   a   positive   integer   or   zero");
  }
  BigDecimal b1 = new BigDecimal(Double.toString(v1));
  BigDecimal b2 = new BigDecimal(Double.toString(v2));
  return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
 }
 
 /**
  * 提供精確的小數位四舍五入處理。
  * 
  * @param v
  *            需要四舍五入的數字
  * @param scale
  *            小數點後保留幾位
  * @return 四舍五入後的結果
  */
 
 public static double round(double v, int scale) {
  if (scale < 0) {
   throw new IllegalArgumentException(
     "The   scale   must   be   a   positive   integer   or   zero");
  }
  BigDecimal b = new BigDecimal(Double.toString(v));
  BigDecimal one = new BigDecimal("1");
  return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
 }
};

Double相加時出現的多位問題

String test = "40.61 ,  18588.73,    29925.07,    7986.06,    18639.19,    25914.32,     32907.74,     34165.89,     9724.7,     52777.92";
 String[] arr = test.split(",");
 double sum = 0;
 for(int i=0;i<arr.length;i++){
 sum += Double.parseDouble(arr[i]);
 }
     System.out.println(sum); 

結果: 230670.22999999998

查瞭查 沒有深入的瞭解,大概是java 的double機制,關於精度的問題,若是不出現這種情況,保留小數點後兩位就可以瞭。或者使用BigDecimal進行計算,另外《effective java》這本書裡也提過,double和float 不建議使用商業計算。

補充:原因在於我們的計算機是二進制的。浮點數沒有辦法是用二進制進行精確表示。

我們的CPU表示浮點數由兩個部分組成:指數和尾數,這樣的表示方法一般都會失去一定的精確度,有些浮點數運算也會產生一定的誤差。

如:2.4的二進制表示並非就是精確的2.4。

反而最為接近的二進制表示是 2.3999999999999999。浮點數的值實際上是由一個特定的數學公式計算得到的。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: