Java中BigDecimal类型的加减乘除及大小比对
前言
在使用Java语言进行商业计算的时候都是使用的java.math.BigDecimal
,其中主要原因为浮点数类型float
和double
运算会丢失精度。
package action;
public class TestBigDecimal {
public static void main(String[] args) {
System.out.println(0.05f + 0.03);
System.out.println(1.13f - 0.62);
System.out.println(2.021f * 100);
System.out.println(202.1f / 100);
System.out.println(Math.round(4.015f * 100) / 100.0);// 4.01 四舍五入保留两位
}
}
运行结果:
从运行结果可以看出,浮点型在进行计算的时候会丢失精度,在商业计算中是不被允许的。
BigDecimal解释
由任意精度的整数非标度值(unscaledValue)
和32位的整数标度(scale)组成。其值为该数的非标度值乘以10的负scale次幂,即为(unscaledValue * 10-scale)
,是不可变的、任意精度的有符号十进制数。
创建BigDecimal类型
在使用BigDecimal
时常用的两个构造函数有两个,一个是用String
,另一个使用double
。
和直接使用浮点型计算一样,在使用构造函数转换为BigDecimal
类型的时候也会存在计算精度降低的情况。
public BigDecimal(String str);
public BigDecimal(double dob);
加减乘除计算
- 加法:add()函数
- 减法:subtract()函数
- 乘法:multiply()函数
- 除法:divide()函数
- 绝对值:abs()函数
首先验证不同的构造方法计算带来的精度问题。
package action;
import java.math.BigDecimal;
public class TestBigDecimal {
public static void main(String[] args) {
/*
* System.out.println(0.05f + 0.03); System.out.println(1.13f - 0.62);
* System.out.println(2.021f * 100); System.out.println(202.1f / 100);
* System.out.println(Math.round(4.015f * 100) / 100.0);// 4.01 四舍五入保留两位
*/
//使用浮点型构造
BigDecimal dob1 = new BigDecimal(0.005);
BigDecimal dob2 = new BigDecimal(1000000);
BigDecimal dob3 = new BigDecimal(-1000000);
//使用字符串构造
BigDecimal str1 = new BigDecimal("0.005");
BigDecimal str2 = new BigDecimal("1000000");
BigDecimal str3 = new BigDecimal("-1000000");
//进行加法
BigDecimal addDob = dob1.add(dob2);
System.out.println("加法用value结果:" + addDob);
BigDecimal addStr = str1.add(str2);
System.out.println("加法用string结果:" + addStr);
//进行减法
BigDecimal subtractDob = dob2.subtract(dob1);
System.out.println("减法用value结果:" + subtractDob);
BigDecimal subtractStr = str2.subtract(str1);
System.out.println("减法用string结果:" + subtractStr);
//进行乘法
BigDecimal multiplyDob = dob1.multiply(dob2);
System.out.println("乘法用value结果:" + multiplyDob);
BigDecimal multiplyStr = str1.multiply(str2);
System.out.println("乘法用string结果:" + multiplyStr);
//进行除法
BigDecimal divideDob = dob2.divide(dob1,20, BigDecimal.ROUND_HALF_UP);
System.out.println("除法用value结果:" + divideDob);
BigDecimal divideStr = str2.divide(str1,20, BigDecimal.ROUND_HALF_UP);
System.out.println("除法用string结果:" + divideStr);
//绝对值
BigDecimal absDob = dob3.abs();
System.out.println("绝对值用value结果:" + absDob);
BigDecimal absStr = str3.abs();
System.out.println("绝对值用string结果:" + absStr);
}
}
执行结果:
结果分析
1、System.out.println()
中的数字的展示默认是使用double
类型的,但double
类型小数部分计算是不精准。
2、在使用double
类型进行BigDecimal
类构造时,计算的结果也是不精确的。
因为不是所有的浮点数都能够被精确的表示成一个double 类型值,因此它会被表示成与它最接近的 double 类型的值。
结论
在构造BigDecimal类
的时候必须使用String
才能够保证高精度的计算结果。
除法的补充
在使用除法divide()
的时候,必须带入相关参数,首先看一下divide()
方法的源码。
/**
* Returns a {@code BigDecimal} whose value is {@code (this /
* divisor)}, and whose scale is as specified. If rounding must
* be performed to generate a result with the specified scale, the
* specified rounding mode is applied.
*
* <p>The new {@link #divide(BigDecimal, int, RoundingMode)} method
* should be used in preference to this legacy method.
*
* @param divisor value by which this {@code BigDecimal} is to be divided.
* @param scale scale of the {@code BigDecimal} quotient to be returned.
* @param roundingMode rounding mode to apply.
* @return {@code this / divisor}
* @throws ArithmeticException if {@code divisor} is zero,
* {@code roundingMode==ROUND_UNNECESSARY} and
* the specified scale is insufficient to represent the result
* of the division exactly.
* @throws IllegalArgumentException if {@code roundingMode} does not
* represent a valid rounding mode.
* @see #ROUND_UP
* @see #ROUND_DOWN
* @see #ROUND_CEILING
* @see #ROUND_FLOOR
* @see #ROUND_HALF_UP
* @see #ROUND_HALF_DOWN
* @see #ROUND_HALF_EVEN
* @see #ROUND_UNNECESSARY
*/
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode) {
if (roundingMode < ROUND_UP || roundingMode > ROUND_UNNECESSARY)
throw new IllegalArgumentException("Invalid rounding mode");
if (this.intCompact != INFLATED) {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intCompact, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intCompact, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
} else {
if ((divisor.intCompact != INFLATED)) {
return divide(this.intVal, this.scale, divisor.intCompact, divisor.scale, scale, roundingMode);
} else {
return divide(this.intVal, this.scale, divisor.intVal, divisor.scale, scale, roundingMode);
}
}
}
其中:divide(BigDecimal divisor, int scale, int roundingMode)
的参数分别代表divisor(除数),scale(精确小数位数),roundingMode(舍入模式)。
另:舍入模式主要有8中如下:
1、ROUND_UP
舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。注意,此舍入模式始终不会减少计算值的大小。
2、ROUND_DOWN
接近零的舍入模式。在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。注意,此舍入模式始终不会增加计算值的大小。
3、ROUND_CEILING
接近正无穷大的舍入模式。如果 BigDecimal
为正,则舍入行为与 ROUND_UP
相同;如果为负,则舍入行为与 ROUND_DOWN
相同。注意,此舍入模式始终不会减少计算值。
4、ROUND_FLOOR
接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与ROUND_DOWN
相同;如果为负,则舍入行为与 ROUND_UP
相同。注意,此舍入模式始终不会增加计算值。
5、ROUND_HALF_UP
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5
,则舍入行为与 ROUND_UP
相同;否则舍入行为与 ROUND_DOWN
相同。注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。
6、ROUND_HALF_DOWN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5
,则舍入行为与 ROUND_UP
相同;否则舍入行为与 ROUND_DOWN
相同(五舍六入)。
7、ROUND_HALF_EVEN
向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP
相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN
相同。注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。
以下例子为保留小数点1位,那么这种舍入方式下的结果:1.15=>1.2、1.25=>1.2
8、ROUND_UNNECESSARY
断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException
。
两个BigDecimal的比对
使用compareTo()
方法进行比较。
该方法用于两个相同数据类型的比较,两个不同类型的数据不能用此方法来比较。
/*
参数
referenceName -- 可以是一个 Byte, Double, Integer, Float, Long 或 Short 类型的参数。
返回值
如果指定的数与参数相等返回0。
如果指定的数小于参数返回 -1。
如果指定的数大于参数返回 1。
*/
//语法
public int compareTo( NumberSubClass referenceName )
//对上面的计算值比对大小
System.out.println(addDob.compareTo(addStr)); //1
System.out.println(subtractDob.compareTo(subtractStr)); //-1
System.out.println(absDob.compareTo(absStr)); //0
//结果:1、-1、0
//表示addDob大于addStr,subtractDob小于subtractStr,absDob等于absStr