内部结构
BigDemical
的核心思想是:把浮点数放大为一个整数进行计算,同时保留相关的精度信息。它解决了 float
和 double
类型在表示浮点数时精度丢失的问题。
BigDemical
由一个 unscaled value 和一个 scale 组成,其中 unscaled value 为 BigInteger
类型,scale 为 int
类型。对应表示的数为:
可以看出 scale 表示的是小数点右边的位数, BigDemical
内部还有一个参数 precision,表示有效数字的长度。
number | unscaled value | scale | precision |
---|---|---|---|
0 | 0 | 0 | 1 |
-0 | 0 | 0 | 1 |
0.00 | 0 | 2 | 1 |
0E+7 | 0 | -7 | 1 |
123 | 123 | 0 | 3 |
-123 | -123 | 0 | 3 |
0.00123 | 123 | 5 | 3 |
1.23E3 | 123 | -1 | 3 |
-1.23E-12 | -123 | 14 | 3 |
BigDecimal
类分别提供了 unscaledValue()
、 scale()
、 precision()
方法去获取对应的值。
构造方法
BigDemical
类有多个构造方法,下面是两个方法的入参分别为 String
类型和 double
类型:
public BigDecimal(String val) {...} // 优先使用
public BigDecimal(double val) {...} // 不要使用该构造方法
由于计算机无法准确表示浮点数,所以不要使用入参为 double
类型的构造方法,而优先使用入参为 String
类型的构造方法。
如果入参必须为 double
类型,可以使用 BigDecimal
类提供的如下静态方法:
public static BigDecimal valueOf(double val) { // 如果入参必须为 double 类型,则使用该方法
return new BigDecimal(Double.toString(val));
}
四则运算
BigDecimal
对象必须使用对应的方法才能进行四则运算,同时还需要考虑结果的 scale 值。
运算 | 基本方法 | Preferred Scale of Result |
---|---|---|
加 | add() |
max(addend.scale(), augend.scale()) |
减 | subtract() |
max(minuend.scale(), subtrahend.scale()) |
乘 | multiply() |
multiplier.scale() + multiplicand.scale() |
除 | divide() |
dividend.scale() - divisor.scale() |
开方 | sqrt() |
radicand.scale() / 2 |
加、减、乘运算都不会破坏对象的精度,但除操作可能存在除不尽的情况,这就需要指明结果的精度和舍入策略。
RoundingMode 类
RoundingMode
是一个定义在 math
包中的枚举类,共定义了 8 种舍入策略。分别为:
舍入策略 | 描述 |
---|---|
UP | 远离 0 |
DOWN | 靠拢 0 |
CEILING | 靠拢正无穷 |
FLOOR | 靠拢负无穷 |
HALF_UP | 四舍五入 |
HALF_DOWN | 五舍六入 |
HALF_EVEN | 银行家舍入 |
UNNECESSARY | 不舍入,除不尽直接报错 |
以上各种舍入策略中,除了 HALF_EVEN
比较难以理解外,其它策略都比较好理解,下面详细介绍一下该策略。
HALF_EVEN
策略的舍入规则如下:
- 向“最近的”数字舍入;
- 如果与两个相邻数字的距离相等,则向相邻的偶数舍入;
- 如果与两个相邻数字的距离不相等,则看舍弃部分的最左边那个数:如果最左边的数字为奇数,则舍入行为与 ROUND_HALF_UP (四舍五入)相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN(五舍六入)相同。 | 对照组 | 格式化前 | 格式化模式 | 格式化后 | 说明 | | :—-: | :—-: | :—-: | :—-: | :—-: | | 1 | 1.15 | #.# | 1.2 | 与两个相邻数字的距离相同,则向相邻的偶数舍入。 | | | 1.25 | | 1.2 | | | 2 | 1.251 | | 1.3 | 舍弃部分最左边的数为奇数,舍入行为与四舍五入类似。 | | | 1.231 | | 1.2 | | | 3 | 1.261 | | 1.3 | 舍弃部分最左边的数为偶数,舍入行为与五舍六入类似。 | | | 1.241 | | 1.2 | |
关于 RoundingMode
类的更详细解释,可以参看官方文档。
divide 方法
BigDecimal
类提供了多个 divide()
方法,常用的有:
public BigDecimal divide(BigDecimal divisor)
该方法结果的首选 scale 为 this.scale() - divisor.scale()
,如果得到商是个无穷数(除数不能被整除)直接抛出 ArithmeticException
异常。
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode)
该方法除了传入除数,还传入了一个 RoundingMode
对象,用于表示使用的舍入策略。源代码如下所示:
public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) {
return this.divide(divisor, scale, roundingMode.oldMode);
}
从源码可以看出,商的 scale 等于被除数的 scale。
算式 | 舍入策略 | 商 | unscaled value | scale | presion |
---|---|---|---|---|---|
1 / 4.0 | Rounding.HALF_UP | 0 | 0 | 0 | 1 |
1.0 / 4.0 | 0.3 | 3 | 1 | 1 | |
1.00 / 4.0 | 0.25 | 25 | 2 | 2 | |
1.000 / 4.0 | 0.250 | 250 | 3 | 3 |
public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
与前一个方法相比,该方法多传入了一个 scale 参数,用于指定商的 scale 值。
equals 方法与 compareTo 方法
BigDecimal
类重写了 equals
方法,当且仅当两个 BigDecimal
对象表示的数值和 scale 都相同时,返回为 true
,如果两个对象表示的数值相同,但 scale 不同,返回 false
。如下例所示:
boolean b1 = new BigDecimal("1").equals(new BigDecimal("1.0")); // false
boolean b2 = new BigDecimal("1.0").equals(new BigDecimal("1.0")); // true
如果希望只比较两个对象表示的数值,可以使用 compareTo()
方法,如下例所示:
int i1 = new BigDecimal("1").compareTo(new BigDecimal("1.0")); // true
int i2 = new BigDecimal("1.0").compareTo(new BigDecimal("1.0")); // true