本文综合网上牛人以及Java API所写,还有自己的一些项目经历。主要实用工具Eclipse3.5、jdk1.6,测试框架JUnit4等。
float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用java.math.BigDecimal。
-----《Effective Java》
看一个小例子:
/** * 浮点数简单计算 * * @author mark */ public void calculate() { System.out.println(0.06 + 0.01); System.out.println(0.3 - 0.4); System.out.println(6.0155 * 100); System.out.println(156.3 / 100); } /** * 测试代码 * * @author mark */ @Test public void testCalculate() { orr.calculate(); }
测试结果:
0.06999999999999999 -0.10000000000000003 601.5500000000001 1.5630000000000002
让人不可思议,怎么会与自己的想象差的那么远!但,现实确实如此!
Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。
再者,Math类、DecimalFormat类有对应的处理数据的方法。实例代码:
System.out.println(Math.round(4.015*100)/100.0); System.out.println(Math.round(4.016*100)/100.0); System.out.println(new DecimalFormat("0.00").format(4.025));
显示结果:
4.01 4.02 4.02
哎,还是让人很失望。
难道在工程中,我们就这样计算吗?当然不是,Java还没有“挫”,呵呵!下面谈谈BigDecimal。
BigDecimal有很多构造方法,详看api文档。这里主要说说下面两个构造方法:
BigDecimal(double val) Translates a double into a BigDecimal which is the exact decimal representation of the double's binary floating-point value. BigDecimal(String val) Translates the string representation of a BigDecimal into a BigDecimal.
关于其他的构造方法,这里不赘述,举一反三吧!
这两个构造方法,建议采用第二个,具体原因在api上解释的很清楚。
BigDecimal public BigDecimal(double val) 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. Notes: 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. 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. 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. Parameters: val - double value to be converted to BigDecimal. Throws: NumberFormatException - if val is infinite or NaN.
翻译一下:
BigDecimal public BigDecimal(double val) 将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。返回的 BigDecimal 的标度是使 (10scale × val) 为整数的最小值。 注: 此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。 另一方面,String 构造方法是完全可预知的:写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。 当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。 参数: val - 要转换为 BigDecimal 的 double 值。 抛出: NumberFormatException - 如果 val 为无穷大或 NaN。
使用BigDecimal,可以获得交准确的计算结果,并且可以自己定义小数精度,具有一定的灵活性。实例代码:
/** * 对浮点数进行准确的除法计算 * * @param number * 被除数(被除数÷除数=商) * @return 商值 */ public double overFormat(double number) { // 被除数 BigDecimal dividend = new BigDecimal(Double.toString(number)); // 除数 BigDecimal divider = new BigDecimal(Double.toString(1)); // ROUND_UP参数表示只要小数点后面第一位不是0,都会被自动进位如192.1-->193.0 return dividend.divide(divider, 0, BigDecimal.ROUND_UP).doubleValue(); }
其中,BigDecimal有很多常量字段。ROUND_HALF_UP表示四舍五入。实例代码:
/** * 对浮点数进行准确除法的计算 * * @param precision * 精度值,保留小数点后几位 * @param number * 被除数(被除数÷除数=商) * @return 商值 */ public double format(int precision, double number) { BigDecimal dividend = new BigDecimal(Double.toString(number)); BigDecimal divider = new BigDecimal(Double.toString(4)); // b1除以b2,如果商是循环小数(除不尽)保留小数点位数为precision,BigDecimal.ROUND_HALF_UP表示四舍五入 // 例如在197/7=28.142857143,经过该方法(precision=3)处理为28.143 // 如果可以除尽,并且precision值大于实际小数点后位数则保留实际位数。若precision小于实际小数点后位数则先四舍五入保留precision位 return dividend.divide(divider, precision, BigDecimal.ROUND_HALF_UP) .doubleValue(); }
在JUnit4的帮助下,很好地完成了许多测试,解开了很多疑惑。如果你不明白最好还是多加测试。也许,你有更多的发现。
在好奇心的驱使下,又看看BigDecimal源码,主要涉及到其valueOf(double val)方法,该方法是静态方法,源码如下:
public static BigDecimal valueOf(double val) { // Reminder: a zero double returns '0.0', so we cannot fastpath // to use the constant ZERO. This might be important enough to // justify a factory approach, a cache, or a few private // constants, later. return new BigDecimal(Double.toString(val)); }
可以看出,valueOf(double val)方法以BigDecimal(Double.toString(val))方式返回一个BigDecimal对象。所以在以double为源的时候创
建BigDecimal对象,可以采用该方法。修改上面实例代码如下:
/** * 对浮点数进行准确的除法计算 * * @param number * 被除数(被除数÷除数=商) * @return 商值 */ public double overFormat(double number) { // 被除数 BigDecimal dividend = BigDecimal.valueOf(number);//new BigDecimal(Double.toString(number)); // 除数 BigDecimal divider = BigDecimal.valueOf(1.0);//new BigDecimal(Double.toString(1)); // ROUND_UP参数表示只要小数点后面第一位不是0,都会被自动进位如192.1-->193.0 return dividend.divide(divider, 0, BigDecimal.ROUND_UP).doubleValue(); }
在看源代码的时候,经常看到别人使用valueof**方法,难道它有什么与众不同吗?研究一下ing.....
首先,研究一下Integer的创建方式:
public static void main(String[] args) { //自动装箱 Integer a = 100; Integer b = 100; System.out.println("equals--" + a.equals(b));//true System.out.println(a == b);//true //new Integer c = new Integer(100); Integer d = new Integer(100); System.out.println("equals--" + c.equals(d));//true System.out.println(c == d);//false //valueOf Integer e = Integer.valueOf(100); Integer f = Integer.valueOf(100); System.out.println("equals--" + e.equals(f));//true System.out.println(e == f);//true //valueOf Integer m = Integer.valueOf(200); Integer n = Integer.valueOf(200); System.out.println("equals--" + m.equals(n));//true System.out.println(m == n);//false }
根据注释来看,equals比较之后都是true,这很好理解。因为Integer重写了equals方法,只要内容相同,equals比较就是true。再者new方式产出的对象用”=“比较肯定是false,但是为什么同样是valueOf,一个是true,而另一个却是false???!!!
不要急,看看Integer源码:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
IntegerCache是静态内部类。从源码可以看出使用valueOf方法创建Integer实例时:
当这个值在-128和127之间时,会用缓存保存起来,供多次使用,以节约内存。
如果不在这个范围内,则创建一个新的Integer对象。再看一个实例:
//自动装箱 Integer a = 200; Integer b = 200; System.out.println("equals--" + a.equals(b));//true System.out.println(a == b);//false
晕。。居然false,why????
反编译一下这段代码,看看它神秘的面纱。
Integer a = Integer.valueOf(100); Integer b = Integer.valueOf(100); System.out.println("equals--" + a.equals(b)); System.out.println(a == b); Integer c = Integer.valueOf(200); Integer d = Integer.valueOf(200); System.out.println("equals--" + c.equals(d)); System.out.println(c == d);
哦,原来如此。自动装箱使用的就是valueOf方法,额的神啊!那么自动拆箱是什么原理?
Integer b = 100; int a = b; //反编译 Integer b = Integer.valueOf(100); 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为自动装箱与拆箱提供精彩的代码
这篇文章引用了他们的某些代码片段,如果不是他们的无私奉献,估计我没有勇气和能力写出这篇文章,再次感谢他们,向他们学习!!!