一、引言
借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,货币计算往往要求结果精确,这时候可以使用int、long或BigDecimal。本文主要讲述BigDecimal使用过程中的一些陷阱、建议和技巧。
二、不可变性
BigDecimal是不可变类,每一个操作(加减乘除等)都会返回一个新的对象, 下面以加法操作为例。
1
2
3
4
5
|
BigDecimal a = new BigDecimal( "1.22" );
System.out.println( "construct with a String value: " + a);
BigDecimal b = new BigDecimal( "2.22" );
a.add(b); System.out.println( "a plus b is : " + a);
|
我们很容易会认为会输出:
construct with a String value: 1.22
a plus b is :3.44
但实际上a plus b is : 1.22
下面我们就来分析一下加法操作的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
public BigDecimal add(BigDecimal augend) {
long xs = this .intCompact; //整型数字表示的BigDecimal,例a的intCompact值为122
long ys = augend.intCompact; //同上
BigInteger fst = ( this .intCompact !=INFLATED) ? null : this .intVal; //初始化BigInteger的值,intVal为BigDecimal的一个BigInteger类型的属性
BigInteger snd =(augend.intCompact !=INFLATED) ? null : augend.intVal;
int rscale = this .scale; //小数位数
long sdiff = ( long )rscale - augend.scale; //小数位数之差
if (sdiff != 0 ) { //取小数位数多的为结果的小数位数
if (sdiff < 0 ) {
int raise =checkScale(-sdiff);
rscale =augend.scale;
if (xs ==INFLATED ||(xs = longMultiplyPowerTen(xs,raise)) ==INFLATED)
fst =bigMultiplyPowerTen(raise);
} else {
int raise =augend.checkScale(sdiff);
if (ys ==INFLATED ||(ys =longMultiplyPowerTen(ys,raise)) ==INFLATED)
snd = augend.bigMultiplyPowerTen(raise);
}
}
if (xs !=INFLATED && ys !=INFLATED) {
long sum = xs + ys;
if ( (((sum ^ xs) &(sum ^ ys))) >= 0L) //判断有无溢出
return BigDecimal.valueOf(sum,rscale); //返回使用BigDecimal的静态工厂方法得到的BigDecimal实例
}
if (fst == null )
fst =BigInteger.valueOf(xs); //BigInteger的静态工厂方法
if (snd == null )
snd =BigInteger.valueOf(ys);
BigInteger sum =fst.add(snd);
return (fst.signum == snd.signum) ? new BigDecimal(sum,INFLATED, rscale, 0 ) :
new BigDecimal(sum,compactValFor(sum),rscale, 0 ); //返回通过其他构造方法得到的BigDecimal对象
} |
因为BigInteger与BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以a.add(b)虽然做了加法操作,但是a并没有保存加操作后的值,正确的用法应该是a=a.add(b); 减乘除操作也是一样的返回一个新的BigDecimal对象。
三、构造函数和valueOf方法
首先看如下一段代码:
1
2
3
4
5
6
7
8
9
10
11
|
// use constructor BigDecimal(double) BigDecimal aDouble = new BigDecimal( 1.22 );
System.out.println( "construct with a double value: " + aDouble);
// use constructor BigDecimal(String) BigDecimal aString = new BigDecimal( "1.22" );
System.out.println( "construct with a String value: " + aString);
// use constructor BigDecimal.valueOf(double) BigDecimal aValue = BigDecimal.valueOf( 1.22 );
System.out.println( "use valueOf method: " + aValue);
|
你认为输出结果会是什么呢?如果你认为第一个会输出1.22,那么恭喜你答错了,输出结果如下:
construct with a double value: 1.2199999999999999733546474089962430298328399658203125
construct with a String value: 1.22
use valueOf method: 1.22
为什么会这样呢?JavaDoc对于BigDecimal(double)有很详细的说明:
1、参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中new BigDecimal(0.1)所创建的BigDecimal的值正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于0.1000000000000000055511151231257827021181583404541015625。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2、另一方面,String 构造方法是完全可预知的:new BigDecimal("0.1") 将创建一个 BigDecimal,它的值正好等于期望的0.1。因此,比较而言,通常建议优先使用String构造方法。
3、当 double 必须用作BigDecimal的来源时,请注意,此构造方法提供了一个精确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法将double转换为String,然后使用BigDecimal(String)构造方法。要获取该结果,使用static valueOf(double)方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** * Translates a {@code long} value into a {@code BigDecimal}
* with a scale of zero. This {@literal "static factory method"}
* is provided in preference to a ({@code long}) constructor
* because it allows for reuse of frequently used
* {@code BigDecimal} values.
*
* @param val value of the {@code BigDecimal}.
* @return a {@code BigDecimal} whose value is {@code val}.
*/
public static BigDecimal valueOf( long val) {
if (val >= 0 && val < zeroThroughTen.length)
return zeroThroughTen[( int )val];
else if (val != INFLATED)
return new BigDecimal( null , val, 0 , 0 );
return new BigDecimal(INFLATED_BIGINT, val, 0 , 0 );
} // Cache of common small BigDecimal values. private static final BigDecimal zeroThroughTen[] = {
new BigDecimal(BigInteger.ZERO, 0 , 0 , 1 ),
new BigDecimal(BigInteger.ONE, 1 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 2 ), 2 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 3 ), 3 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 4 ), 4 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 5 ), 5 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 6 ), 6 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 7 ), 7 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 8 ), 8 , 0 , 1 ),
new BigDecimal(BigInteger.valueOf( 9 ), 9 , 0 , 1 ),
new BigDecimal(BigInteger.TEN, 10 , 0 , 2 ),
}; |
附上相应的测试代码:
1
2
3
4
5
6
7
|
BigDecimal a1 = BigDecimal.valueOf( 10 );
BigDecimal a2 = BigDecimal.valueOf( 10 );
System.out.println(a1 == a2); // true
BigDecimal a3 = BigDecimal.valueOf( 11 );
BigDecimal a4 = BigDecimal.valueOf( 11 );
System.out.println(a3 == a4); // false
|
四、equals方法
BigDecimal.equals方法是有问题的.仅当你确定比较的值有着相同的标度时才可使用. 因此,当你校验相等性时注意 - BigDecimal有一个标度,用于相等性比较. 而compareTo方法则会忽略这个标度(scale).
BigDecimal的equals方法源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@Override public boolean equals(Object x) {
// 必须是BigDecimal实例
if (!(x instanceof BigDecimal))
return false ;
BigDecimal xDec = (BigDecimal) x;
if (x == this )
return true ;
// 标度必须相同
if (scale != xDec.scale)
return false ;
long s = this .intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor( this .intVal);
return this .inflated().equals(xDec.inflated());
} |
参见以下测试代码:
1
2
3
4
5
6
7
8
|
// 打印false System.out.println( new BigDecimal( "0.0" ).equals( new BigDecimal( "0.00" )));
// 打印false System.out.println( new BigDecimal( "0.0" ).hashCode() == ( new BigDecimal( "0.00" )).hashCode());
// 打印0 System.out.println( new BigDecimal( "0.0" ).compareTo( new BigDecimal( "0.00" )));
|
五、对除法使用标度
参见以下测试代码:
1
2
3
4
5
6
7
8
9
10
|
//java.lang.ArithmeticException: Non-terminating decimal expansion; //no exact representable decimal result. try {
BigDecimal.valueOf( 1 ).divide(BigDecimal.valueOf( 3 ));
} catch (ArithmeticException ex) {
System.out.println(ex.getMessage());
} // always use a scale and the rounding mode of your choice // 0.33 System.out.println(BigDecimal.valueOf( 1 ).divide(BigDecimal.valueOf( 3 ), 2 , BigDecimal.ROUND_HALF_UP));
|
六、总结
(1)商业计算使用BigDecimal。
(2)使用参数类型为String的构造函数,将double转换成BigDecimal时用BigDecimal.valueOf(double),做除法运算时使用重载的方法divide(BigDecimal d, int scale, int roundMode)。
(3)BigDecimal是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做加减乘除运算时千万要保存操作后的值。
(4)尽量使用compareTo方法比较两个BigDecimal对象的大小。
七、参考资料
《Effective Java》
http://www.stichlberger.com/software/java-bigdecimal-gotchas/
http://stackoverflow.com/questions/7186204/bigdecimal-to-use-new-or-valueof
http://www.javaworld.com/article/2073176/caution--double-to-bigdecimal-in-java.html
相关推荐
借用《Effactive Java》这本书中的话,float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供...
Java Bigdecimal使用原理详解 Java Bigdecimal是Java语言中用于精确计算的类,它可以完善float和double类无法进行精确计算的缺憾。BigDecimal类位于java.math类包下,提供了多种构造函数和方法来实现精确计算。 ...
### BigDecimal详解、代码示例和经常遇到的坑 #### 一、BigDecimal概述 `BigDecimal`是Java编程语言中`java.math`包内提供的一种高级数据类型,主要用于处理高精度的十进制数值。与基本数据类型`float`或`double`...
Java中的`BigDecimal`类是用来表示任意精度的十进制数,尤其适合于需要精确计算的商业和财务场景。它的核心概念包括非标度值(unscaled value)和标度(scale),非标度值是一个任意精度的整数,标度则是小数点后的...
Java中BigDecimal类的使用详解 Java中BigDecimal类是Java.math包中提供的一个API类,用于对超过16位有效位的数进行精确的运算。由于浮点数的精度问题,Java中浮点数的计算会失去一定的精确度。因此,使用BigDecimal...
Java中BigDecimal的基本运算详解 Java中的BigDecimal是一种高精度的数据类型,它可以用来表示非常大的整数和小数,提供了丰富的数学运算功能。下面我们将对Java中BigDecimal的基本运算进行详细的介绍。 构造方法 ...
BigDecimal 类详解 BigDecimal 类是 Java 中的一种数值类型,主要用于处理超过 16 位有效数字的数值运算。该类提供了多种构造器和方法,用于创建和操作 BigDecimal 对象。 构造器 BigDecimal 类提供了四种构造器...
031110_【第11章:Java常用类库】_大数操作(BigIntger、BigDecimal)笔记.pdf 031111_【第11章:Java常用类库】_对象克隆技术笔记.pdf 031112_【第11章:Java常用类库】_Arrays笔记.pdf 031113_【第11章:Java常用...
### Java保留两位小数的方法详解 #### 方法一:使用`BigDecimal` **原理**:通过`BigDecimal`类提供的方法,能够精确地控制数值的精度。在本案例中,使用`setScale`方法来设置小数点后保留的位数,并指定舍入规则...
BigDecimal 详解 Java 魔法类 Unsafe 详解 Java SPI 机制详解 Java 语法糖详解 集合 知识点/面试题总结: Java 集合常见知识点&面试题总结(上) (必看 ) Java 集合常见知识点&面试题总结(下) (必看 ) Java 容器使用...
BigDecimal 详解 Java 魔法类 Unsafe 详解 Java SPI 机制详解 Java 语法糖详解 集合 知识点/面试题总结 : Java 集合常见知识点&面试题总结(上) (必看 ) Java 集合常见知识点&面试题总结(下) (必看 ) Java 容器使用...
BigDecimal 详解 Java 魔法类 Unsafe 详解 Java SPI 机制详解 Java 语法糖详解 集合 知识点/面试题总结: Java 集合常见知识点&面试题总结(上) (必看 ) Java 集合常见知识点&面试题总结(下) (必看 ) Java 容器使用...
### Java中的BigDecimal类详解 在Java编程语言中,当我们处理涉及货币、财务或者任何需要高精度计算的场景时,`BigDecimal` 类是非常重要的工具之一。本文将深入探讨 `BigDecimal` 类的基本概念、特点以及如何使用...
Java序列化详解泛型&通配符详解Java 引用机制详解Java代理模式详解BigDecimal 详细解Java 魔法类 Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看...
在Java中使用正则表达式来判断字符串是否符合整数、小数或实数的格式是一种常见且有效的做法。在编程中,我们经常需要对输入的字符串进行格式验证,以确保它们符合预期的数值格式,尤其是在处理财务数据、用户输入...
Java Bug模式详解主要涵盖的是Java编程中常见的错误和陷阱,这些错误往往会导致程序运行异常或者性能下降。在软件开发过程中,理解和避免这些Bug模式对于提高代码质量和效率至关重要。本资源包含一本PDF电子书《Java...
Java序列化详解泛型&通配符详解Java 引用机制详解Java代理模式详解BigDecimal 详细解Java 魔法类 Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看...
Java实现搜索功能代码详解 Java实现搜索功能代码详解是一篇详细介绍了Java实现搜索功能代码的文章,主要讲解了如何使用Java语言实现搜索功能的详细思路和代码实现。下面我们将详细介绍其中的知识点。 搜索框的GET...
### Java保留两位小数问题详解 在Java编程中,处理数值型数据时,经常会遇到需要对数字进行格式化的需求,尤其是对于浮点数的操作。本文将深入探讨如何在Java中保留两位小数,并覆盖四舍五入、浮点数精确计算以及...