1. 程式人生 > >Java精確運算(BigDecimal)

Java精確運算(BigDecimal)

necessary ring ext col ons 詳細 opera 字段 str

(1)、浮點數精確計算  
勝利油田三流合一項目中一直存在一個問題,就是每次報表統計的物資金額和實際的金額要差那麽幾分錢,和實際金額不一致,讓客戶覺得總是不那麽舒服,
原因是因為我們使用java的浮點類型double來定義物資金額,並且在報表統計中我們經常要進行一些運算,但Java中浮點數(
doublefloat)的計算是非精確計算,請看下面一個例子: 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 這個問題就非常嚴重了,如果你有123.3元要購買商品,而計算機卻認為你只有123.29999999999999元,錢不夠,計算機拒絕交易。 (2)、四舍五入 是否可以四舍五入呢?當然可以,習慣上我們本能就會這樣考慮,但四舍五入意味著誤差,商業運算中可能意味著錯誤,
同時Java中也沒有提供保留指定位數的四舍五入方法,只提供了一個Math.round(
double d)和Math.round(float
f)的方法,分別返回長整型和整型值。round方法不能設置保留幾位小數,我們只能象這樣(保留兩位): public double round(double value){ return Math.round( value * 100 ) / 100.0; } 但非常不幸的是,上面的代碼並不能正常工作,給這個方法傳入4.015它將返回4.01而不是4.02,如我們在上面看到的 4.015 * 100 = 401.49999999999994 因此如果我們要做到精確的四舍五入,這種方法不能滿足我們的要求。 還有一種方式是使用java.text.DecimalFormat,但也存在問題,format采用的舍入模式是ROUND_HALF_DOWN(舍入模式在下面有介紹),
比如說4.025保留兩位小數會是4.
02,因為.025距離” nearest neighbor”(.02和.03)長度是相等,向下舍入就是.02,如果是4.0251那麽保留兩位小數就是4.03。 System.out.println(new java.text.DecimalFormat("0.00").format(4.025)); System.out.println(new java.text.DecimalFormat("0.00").format(4.0251)); 輸出是 4.02 4.033)、浮點數輸出(科學記數法) Java浮點型數值在大於9999999.0就自動轉化為科學記數法來表示,我們看下面的例子: System.out.println(999999999.04); System.out.println(99999999.04); System.out.println(10000000.01); System.out.println(9999999.04); 輸出的結果如下: 9.9999999904E8 9.999999904E7 1.000000001E7 9999999.04 但有時我們可能不需要科學記數法的表示方法,需要轉換為字符串,還不能直接用toString()等方法轉換,很煩瑣。 BigDecimal介紹 BigDecimal是Java提供的一個不變的、任意精度的有符號十進制數對象。它提供了四個構造器,有兩個是用BigInteger構造,
在這裏我們不關心,我們重點看用double和String構造的兩個構造器(有關BigInteger詳細介紹請查閱j2se API文檔)。 BigDecimal(
double val) Translates a double into a BigDecimal. BigDecimal(String val) Translates the String representation of a BigDecimal into a BigDecimal. BigDecimal(double)是把一個double類型十進制數構造為一個BigDecimal對象實例。 BigDecimal(String)是把一個以String表示的BigDecimal對象構造為BigDecimal對象實例。 習慣上,對於浮點數我們都會定義為double或float,但BigDecimal API文檔中對於BigDecimal(double)有這麽一段話: Note: the results of this constructor can be somewhat unpredictable. One might assume that new BigDecimal(.1)
is exactly equal to .1, but it is actually equal to .10000000000000000555111512312578 27021181583404541015625.
This is so because .1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of
any finite length). Thus, the long value that is being passed in to the constructor is not exactly equal to .1, appearances notwithstanding. The (String) constructor, on the other hand, is perfectly predictable: new BigDecimal(".1") is exactly equal to .1,
as one would expect. Therefore, it is generally recommended that the (String) constructor be used in preference to this one 下面對這段話做簡單解釋: 註意:這個構造器的結果可能會有不可預知的結果。有人可能設想new BigDecimal(.1)等於.1是正確的,
但它實際上是等於.1000000000000000055511151231257827021181583404541015625,這就是為什麽.1不能用一個double精確表示的原因,
因此,這個被放進構造器中的長值並不精確的等於.1,盡管外觀看起來是相等的。 然而(String)構造器,則完全可預知的,new BigDecimal(“.1”)如同期望的那樣精確的等於.1,因此,(String)構造器是被優先推薦使用的。 看下面的結果: System.out.println(new BigDecimal(123456789.02).toString()); System.out.println(new BigDecimal("123456789.02").toString()); 輸出為: 123456789.01999999582767486572265625 123456789.02 現在我們知道,如果需要精確計算,非要用String來夠造BigDecimal不可! 實現方案 現在我們已經知道怎麽解決這個問題了,原則上是使用BigDecimal(String)構造器,我們建議,在商業應用開發中,
涉及金額等浮點數計算的數據,全部定義為String,數據庫中可定義為字符型字段,在需要使用這些數據進行運算的時候,
使用BigDecimal(String)構造BigDecimal對象進行運算,保證數據的精確計算。同時避免了科學記數法的出現。
如果科學記數表示法在應用中不是一種負擔的話,可以考慮定義為浮點類型。 這裏我們提供了一個工具類,定義浮點數的加、減、乘、除和四舍五入等運算方法。以供參考。 源文件MathExtend.java:

  1 import java.math.BigDecimal;  
  2 public class MathExtend  
  3 {  
  4   //默認除法運算精度  
  5   private static final int DEFAULT_DIV_SCALE = 10;  
  6    
  7  /** 
  8   * 提供精確的加法運算。 
  9   * @param v1 
 10   * @param v2 
 11   * @return 兩個參數的和 
 12   */  
 13   public static double add(double v1, double v2)  
 14   {  
 15       BigDecimal b1 = new BigDecimal(Double.toString(v1));  
 16       BigDecimal b2 = new BigDecimal(Double.toString(v2));  
 17       return b1.add(b2).doubleValue();  
 18   }  
 19   /** 
 20    * 提供精確的加法運算 
 21    * @param v1   
 22    * @param v2 
 23    * @return 兩個參數數學加和,以字符串格式返回 
 24    */  
 25   public static String add(String v1, String v2)  
 26   {  
 27       BigDecimal b1 = new BigDecimal(v1);  
 28       BigDecimal b2 = new BigDecimal(v2);  
 29       return b1.add(b2).toString();  
 30   }  
 31    
 32  /** 
 33   * 提供精確的減法運算。 
 34   * @param v1 
 35   * @param v2 
 36   * @return 兩個參數的差 
 37   */  
 38   public static double subtract(double v1, double v2)  
 39   {  
 40       BigDecimal b1 = new BigDecimal(Double.toString(v1));  
 41       BigDecimal b2 = new BigDecimal(Double.toString(v2));  
 42       return b1.subtract(b2).doubleValue();  
 43   }  
 44    
 45   /** 
 46    * 提供精確的減法運算 
 47    * @param v1 
 48    * @param v2 
 49    * @return 兩個參數數學差,以字符串格式返回 
 50    */  
 51   public static String subtract(String v1, String v2)  
 52   {  
 53       BigDecimal b1 = new BigDecimal(v1);  
 54       BigDecimal b2 = new BigDecimal(v2);  
 55       return b1.subtract(b2).toString();  
 56   }  
 57    
 58    
 59   /** 
 60   * 提供精確的乘法運算。 
 61   * @param v1 
 62   * @param v2 
 63   * @return 兩個參數的積 
 64   */  
 65   public static double multiply(double v1, double v2)  
 66   {  
 67       BigDecimal b1 = new BigDecimal(Double.toString(v1));  
 68       BigDecimal b2 = new BigDecimal(Double.toString(v2));  
 69       return b1.multiply(b2).doubleValue();  
 70   }  
 71    
 72   /** 
 73    * 提供精確的乘法運算 
 74    * @param v1 
 75    * @param v2 
 76    * @return 兩個參數的數學積,以字符串格式返回 
 77    */  
 78   public static String multiply(String v1, String v2)  
 79   {  
 80       BigDecimal b1 = new BigDecimal(v1);  
 81       BigDecimal b2 = new BigDecimal(v2);  
 82       return b1.multiply(b2).toString();  
 83   }  
 84    
 85   /** 
 86   * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到 
 87   * 小數點以後10位,以後的數字四舍五入,舍入模式采用ROUND_HALF_EVEN 
 88   * @param v1 
 89   * @param v2 
 90   * @return 兩個參數的商 
 91   */  
 92   public static double divide(double v1, double v2)  
 93   {  
 94       return divide(v1, v2, DEFAULT_DIV_SCALE);  
 95   }  
 96    
 97   /** 
 98    * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 
 99    * 定精度,以後的數字四舍五入。舍入模式采用ROUND_HALF_EVEN 
100    * @param v1 
101    * @param v2 
102    * @param scale 表示需要精確到小數點以後幾位。 
103    * @return 兩個參數的商 
104    */  
105   public static double divide(double v1,double v2, int scale)  
106   {  
107       return divide(v1, v2, scale, BigDecimal.ROUND_HALF_EVEN);  
108   }  
109    
110   /** 
111    * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 
112    * 定精度,以後的數字四舍五入。舍入模式采用用戶指定舍入模式 
113    * @param v1 
114    * @param v2 
115    * @param scale 表示需要精確到小數點以後幾位 
116    * @param round_mode 表示用戶指定的舍入模式 
117    * @return 兩個參數的商 
118    */  
119   public static double divide(double v1,double v2,int scale, int round_mode){  
120           if(scale < 0)  
121           {  
122               throw new IllegalArgumentException("The scale must be a positive integer or zero");  
123           }  
124           BigDecimal b1 = new BigDecimal(Double.toString(v1));  
125           BigDecimal b2 = new BigDecimal(Double.toString(v2));  
126           return b1.divide(b2, scale, round_mode).doubleValue();  
127   }  
128    
129   /** 
130    * 提供(相對)精確的除法運算,當發生除不盡的情況時,精確到 
131    * 小數點以後10位,以後的數字四舍五入,舍入模式采用ROUND_HALF_EVEN 
132    * @param v1 
133    * @param v2 
134    * @return 兩個參數的商,以字符串格式返回 
135    */  
136   public static String divide(String v1, String v2)  
137   {  
138       return divide(v1, v2, DEFAULT_DIV_SCALE);  
139   }  
140    
141   /** 
142    * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 
143    * 定精度,以後的數字四舍五入。舍入模式采用ROUND_HALF_EVEN 
144    * @param v1 
145    * @param v2 
146    * @param scale 表示需要精確到小數點以後幾位 
147    * @return 兩個參數的商,以字符串格式返回 
148    */  
149   public static String divide(String v1, String v2, int scale)  
150   {  
151       return divide(v1, v2, DEFAULT_DIV_SCALE, BigDecimal.ROUND_HALF_EVEN);  
152   }  
153    
154   /** 
155    * 提供(相對)精確的除法運算。當發生除不盡的情況時,由scale參數指 
156    * 定精度,以後的數字四舍五入。舍入模式采用用戶指定舍入模式 
157    * @param v1 
158    * @param v2 
159    * @param scale 表示需要精確到小數點以後幾位 
160    * @param round_mode 表示用戶指定的舍入模式 
161    * @return 兩個參數的商,以字符串格式返回 
162    */  
163   public static String divide(String v1, String v2, int scale, int round_mode)  
164   {  
165       if(scale < 0)  
166       {  
167           throw new IllegalArgumentException("The scale must be a positive integer or zero");  
168       }  
169       BigDecimal b1 = new BigDecimal(v1);  
170       BigDecimal b2 = new BigDecimal(v2);  
171       return b1.divide(b2, scale, round_mode).toString();  
172   }  
173    
174   /** 
175    * 提供精確的小數位四舍五入處理,舍入模式采用ROUND_HALF_EVEN 
176    * @param v 需要四舍五入的數字 
177    * @param scale 小數點後保留幾位 
178    * @return 四舍五入後的結果 
179    */  
180   public static double round(double v,int scale)  
181   {  
182       return round(v, scale, BigDecimal.ROUND_HALF_EVEN);  
183   }  
184   /** 
185    * 提供精確的小數位四舍五入處理 
186    * @param v 需要四舍五入的數字 
187    * @param scale 小數點後保留幾位 
188    * @param round_mode 指定的舍入模式 
189    * @return 四舍五入後的結果 
190    */  
191   public static double round(double v, int scale, int round_mode)  
192   {  
193      if(scale<0)  
194      {  
195          throw new IllegalArgumentException("The scale must be a positive integer or zero");  
196      }  
197      BigDecimal b = new BigDecimal(Double.toString(v));  
198      return b.setScale(scale, round_mode).doubleValue();  
199   }  
200    
201   /** 
202    * 提供精確的小數位四舍五入處理,舍入模式采用ROUND_HALF_EVEN 
203    * @param v 需要四舍五入的數字 
204    * @param scale 小數點後保留幾位 
205    * @return 四舍五入後的結果,以字符串格式返回 
206    */  
207   public static String round(String v, int scale)  
208   {  
209     return round(v, scale, BigDecimal.ROUND_HALF_EVEN);  
210   }  
211   /** 
212    * 提供精確的小數位四舍五入處理 
213    * @param v 需要四舍五入的數字 
214    * @param scale 小數點後保留幾位 
215    * @param round_mode 指定的舍入模式 
216    * @return 四舍五入後的結果,以字符串格式返回 
217    */  
218   public static String round(String v, int scale, int round_mode)  
219   {  
220      if(scale<0)  
221      {  
222          throw new IllegalArgumentException("The scale must be a positive integer or zero");  
223      }  
224      BigDecimal b = new BigDecimal(v);  
225      return b.setScale(scale, round_mode).toString();  
226   }  
227 }  
BigDecimal 舍入模式(Rounding mode)介紹:  
  
BigDecimal定義了一下舍入模式,只有在作除法運算或四舍五入時才用到舍入模式,下面簡單介紹,詳細請查閱J2se API文檔  
static int  
ROUND_CEILING  
          Rounding mode to round towards positive infinity.  
向正無窮方向舍入  
static int  
ROUND_DOWN  
          Rounding mode to round towards zero.  
向零方向舍入  
static int  
ROUND_FLOOR  
          Rounding mode to round towards negative infinity.  
向負無窮方向舍入  
static int  
ROUND_HALF_DOWN  
          Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.  
向(距離)最近的一邊舍入,除非兩邊(的距離)是相等,如果是這樣,向下舍入, 例如1.55 保留一位小數結果為1.5  
static int  
ROUND_HALF_EVEN  
          Rounding mode to round towards the "nearest neighbor" unless both neighbors are equidistant, in which case, round towards the even neighbor.  
向(距離)最近的一邊舍入,除非兩邊(的距離)是相等,如果是這樣,如果保留位數是奇數,使用ROUND_HALF_UP ,如果是偶數,使用ROUND_HALF_DOWN  
static int  
ROUND_HALF_UP  
          Rounding mode to round towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.  
向(距離)最近的一邊舍入,除非兩邊(的距離)是相等,如果是這樣,向上舍入, 1.55保留一位小數結果為1.6  
static int  
ROUND_UNNECESSARY  
          Rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.  
計算結果是精確的,不需要舍入模式  
static int  
ROUND_UP  
          Rounding mode to round away from zero.  
向遠離0的方向舍入 

轉自: http://blog.csdn.net/guo_love_peng/article/details/7739455

Java精確運算(BigDecimal)