本文綜合網上牛人以及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 為自動裝箱與拆箱提供精彩的代碼
這篇文章引用了他們的某些程式碼片段,如果不是他們的無私奉獻,估計我沒有勇氣和能力寫出這篇文章,再次感謝他們,向他們學習!!!