由BigDecimal引發的思考

來源:互聯網
上載者:User

本文綜合網上牛人以及Java API所寫,還有自己的一些項目經曆。主要工具 + 生產力Eclipse3.5、jdk1.6,測試架構JUnit4等。

float和double只能用來做科學計算或者是工程計算,在商業計算中我們要用java.math.BigDecimal。

                                                                                                                                       -----《Effective Java》

看一個小例子:

/**<br /> * 浮點數簡單計算<br /> *<br /> * @author mark<br /> */<br />public void calculate() {<br />System.out.println(0.06 + 0.01);<br />System.out.println(0.3 - 0.4);<br />System.out.println(6.0155 * 100);<br />System.out.println(156.3 / 100);<br />}<br />/**<br /> * 測試代碼<br /> *<br /> * @author mark<br /> */<br />@Test<br />public void testCalculate()<br />{<br />orr.calculate();<br />} 

測試結果:

0.06999999999999999<br />-0.10000000000000003<br />601.5500000000001<br />1.5630000000000002 

讓人不可思議,怎麼會與自己的想象差的那麼遠!但,現實確實如此!

Java中的簡單浮點數類型float和double不能夠進行運算。不光是Java,在其它很多程式設計語言中也有這樣的問題。在大多數情況下,計算的結果是準確的,但是多試幾次(可以做一個迴圈)就可以試出類似上面的錯誤。

再者,Math類、DecimalFormat類有對應的處理資料的方法。執行個體代碼:

System.out.println(Math.round(4.015*100)/100.0);<br />System.out.println(Math.round(4.016*100)/100.0);<br />System.out.println(new DecimalFormat("0.00").format(4.025)); 

顯示結果:

4.01<br />4.02<br />4.02 

哎,還是讓人很失望。

難道在工程中,我們就這樣計算嗎?當然不是,Java還沒有“挫”,呵呵!下面談談BigDecimal。

BigDecimal有很多構造方法,詳看api文檔。這裡主要說說下面兩個構造方法:

BigDecimal(double val)<br /> Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value.<br />BigDecimal(String val)<br /> Translates the string representation of a BigDecimal into a BigDecimal. 

關於其他的構造方法,這裡不贅述,舉一反三吧!

這兩個構造方法,建議採用第二個,具體原因在api上解釋的很清楚。

BigDecimal<br />public BigDecimal(double val)<br />Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value. The scale of the returned BigDecimal is the smallest value such that (10scale × val) is an integer.<br />Notes:<br />The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.<br />The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which is exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the String constructor be used in preference to this one.<br />When a double must be used as a source for a BigDecimal, note that this constructor provides an exact conversion; it does not give the same result as converting the double to a String using the Double.toString(double) method and then using the BigDecimal(String) constructor. To get that result, use the static valueOf(double) method.<br />Parameters:<br />val - double value to be converted to BigDecimal.<br />Throws:<br />NumberFormatException - if val is infinite or NaN.<br /> 

翻譯一下:

BigDecimal<br />public BigDecimal(double val)<br />將 double 轉換為 BigDecimal,後者是 double 的二進位浮點值準確的十進位表示形式。返回的 BigDecimal 的標度是使 (10scale × val) 為整數的最小值。<br />註:<br />此構造方法的結果有一定的不可預知性。有人可能認為在 Java 中寫入 new BigDecimal(0.1) 所建立的 BigDecimal 正好等於 0.1(非標度值 1,其標度為 1),但是它實際上等於 0.1000000000000000055511151231257827021181583404541015625。這是因為 0.1 無法準確地表示為 double(或者說對於該情況,不能表示為任何有限長度的二進位小數)。這樣,傳入 到構造方法的值不會正好等於 0.1(雖然表面上等於該值)。<br />另一方面,String 構造方法是完全可預知的:寫入 new BigDecimal("0.1") 將建立一個 BigDecimal,它正好 等於預期的 0.1。因此,比較而言,通常建議優先使用 String 構造方法。<br />當 double 必須用作 BigDecimal 的源時,請注意,此構造方法提供了一個準確轉換;它不提供與以下操作相同的結果:先使用 Double.toString(double) 方法,然後使用 BigDecimal(String) 構造方法,將 double 轉換為 String。要擷取該結果,請使用 static valueOf(double) 方法。<br />參數:<br />val - 要轉換為 BigDecimal 的 double 值。<br />拋出:<br />NumberFormatException - 如果 val 為無窮大或 NaN。 

使用BigDecimal,可以獲得交準確的計算結果,並且可以自己定義小數精度,具有一定的靈活性。執行個體代碼:

/**<br /> * 對浮點數進行準確的除法計算<br /> *<br /> * @param number<br /> * 被除數(被除數÷除數=商)<br /> * @return 商值<br /> */<br />public double overFormat(double number) {<br />// 被除數<br />BigDecimal dividend = new BigDecimal(Double.toString(number));<br />// 除數<br />BigDecimal divider = new BigDecimal(Double.toString(1));<br />// ROUND_UP參數表示只要小數點後面第一位不是0,都會被自動進位如192.1-->193.0<br />return dividend.divide(divider, 0, BigDecimal.ROUND_UP).doubleValue();<br />} 

其中,BigDecimal有很多常量欄位。ROUND_HALF_UP表示四捨五入。執行個體代碼:

/**<br /> * 對浮點數進行準確除法的計算<br /> *<br /> * @param precision<br /> * 精度值,保留小數點後幾位<br /> * @param number<br /> * 被除數(被除數÷除數=商)<br /> * @return 商值<br /> */<br />public double format(int precision, double number) {<br />BigDecimal dividend = new BigDecimal(Double.toString(number));<br />BigDecimal divider = new BigDecimal(Double.toString(4));<br />// b1除以b2,如果商是迴圈小數(除不盡)保留小數點位元為precision,BigDecimal.ROUND_HALF_UP表示四捨五入<br />// 例如在197/7=28.142857143,經過該方法(precision=3)處理為28.143<br />// 如果可以除盡,並且precision值大於實際小數點後位元則保留實際位元。若precision小於實際小數點後位元則先四捨五入保留precision位<br />return dividend.divide(divider, precision, BigDecimal.ROUND_HALF_UP)<br />.doubleValue();<br />} 

在JUnit4的協助下,很好地完成了許多測試,解開了很多疑惑。如果你不明白最好還是多加測試。也許,你有更多的發現。

在好奇心的驅使下,又看看BigDecimal源碼,主要涉及到其valueOf(double val)方法,該方法是靜態方法,源碼如下:

public static BigDecimal valueOf(double val) {<br /> // Reminder: a zero double returns '0.0', so we cannot fastpath<br /> // to use the constant ZERO. This might be important enough to<br /> // justify a factory approach, a cache, or a few private<br /> // constants, later.<br /> return new BigDecimal(Double.toString(val));<br /> }<br /> 

可以看出,valueOf(double val)方法以BigDecimal(Double.toString(val))方式返回一個BigDecimal對象。所以在以double為源的時候創

建BigDecimal對象,可以採用該方法。修改上面執行個體代碼如下:

/**<br /> * 對浮點數進行準確的除法計算<br /> *<br /> * @param number<br /> * 被除數(被除數÷除數=商)<br /> * @return 商值<br /> */<br />public double overFormat(double number) {<br />// 被除數<br />BigDecimal dividend = BigDecimal.valueOf(number);//new BigDecimal(Double.toString(number));<br />// 除數<br />BigDecimal divider = BigDecimal.valueOf(1.0);//new BigDecimal(Double.toString(1));<br />// ROUND_UP參數表示只要小數點後面第一位不是0,都會被自動進位如192.1-->193.0<br />return dividend.divide(divider, 0, BigDecimal.ROUND_UP).doubleValue();<br />} 

在看原始碼的時候,經常看到別人使用valueof**方法,難道它有什麼與眾不同嗎?研究一下ing.....

首先,研究一下Integer的建立方式:

public static void main(String[] args) {<br />//自動裝箱<br />Integer a = 100;<br />Integer b = 100;<br />System.out.println("equals--" + a.equals(b));//true<br />System.out.println(a == b);//true</p><p>//new<br />Integer c = new Integer(100);<br />Integer d = new Integer(100);<br />System.out.println("equals--" + c.equals(d));//true<br />System.out.println(c == d);//false</p><p>//valueOf<br />Integer e = Integer.valueOf(100);<br />Integer f = Integer.valueOf(100);<br />System.out.println("equals--" + e.equals(f));//true<br />System.out.println(e == f);//true</p><p>//valueOf<br />Integer m = Integer.valueOf(200);<br />Integer n = Integer.valueOf(200);<br />System.out.println("equals--" + m.equals(n));//true<br />System.out.println(m == n);//false<br />} 

根據注釋來看,equals比較之後都是true,這很好理解。因為Integer重寫了equals方法,只要內容相同,equals比較就是true。再者new方式產出的對象用”=“比較肯定是false,但是為什麼同樣是valueOf,一個是true,而另一個卻是false???!!!

不要急,看看Integer源碼:

public static Integer valueOf(int i) {<br /> if(i >= -128 && i <= IntegerCache.high)<br /> return IntegerCache.cache[i + 128];<br /> else<br /> return new Integer(i);<br /> } 

IntegerCache是靜態內部類。從源碼可以看出使用valueOf方法建立Integer執行個體時:

當這個值在-128和127之間時,會用緩衝儲存起來,供多次使用,以節約記憶體。 
如果不在這個範圍內,則建立一個新的Integer對象。 再看一個執行個體:

//自動裝箱<br />Integer a = 200;<br />Integer b = 200;<br />System.out.println("equals--" + a.equals(b));//true<br />System.out.println(a == b);//false 

暈。。居然false,why????

反編譯一下這段代碼,看看它神秘的面紗。

Integer a = Integer.valueOf(100);<br /> Integer b = Integer.valueOf(100);<br /> System.out.println("equals--" + a.equals(b));<br /> System.out.println(a == b);<br /> Integer c = Integer.valueOf(200);<br /> Integer d = Integer.valueOf(200);<br /> System.out.println("equals--" + c.equals(d));<br /> System.out.println(c == d); 

哦,原來如此。自動裝箱使用的就是valueOf方法,額的神啊!那麼自動拆箱是什麼原理?

Integer b = 100;<br />int a = b;<br />//反編譯<br />Integer b = Integer.valueOf(100);<br />int a = b.intValue(); 

另外,算是提示吧?!

初始化一個字串,  String s1 = "s1"; 這樣的代碼肯定比  String s2 = new String("s2");代碼強,將其他類型的值轉換成String的時候,valueof方法比new方法效率也高。 

文章到此為止,希望對你有用!


非常感謝:

http://www.cnblogs.com/sharewind/archive/2007/09/08/886749.html 提供大量關於BigDecimal的例子代碼

http://honda418.iteye.com/blog/315893 探討了Integer的陷阱 

http://www.cnblogs.com/danne823/archive/2011/04/22/2025332.html 為自動裝箱與拆箱提供精彩的代碼

 

這篇文章引用了他們的某些程式碼片段,如果不是他們的無私奉獻,估計我沒有勇氣和能力寫出這篇文章,再次感謝他們,向他們學習!!! 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.