内部结构

BigDemical 的核心思想是:把浮点数放大为一个整数进行计算,同时保留相关的精度信息。它解决了 floatdouble 类型在表示浮点数时精度丢失的问题。

BigDemical 由一个 unscaled value 和一个 scale 组成,其中 unscaled value 为 BigInteger 类型,scale 为 int 类型。对应表示的数为:
BigDemical - 图1
可以看出 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 类型:

  1. public BigDecimal(String val) {...} // 优先使用
  2. public BigDecimal(double val) {...} // 不要使用该构造方法

由于计算机无法准确表示浮点数,所以不要使用入参为 double 类型的构造方法,而优先使用入参为 String 类型的构造方法。

如果入参必须为 double 类型,可以使用 BigDecimal 类提供的如下静态方法:

  1. public static BigDecimal valueOf(double val) { // 如果入参必须为 double 类型,则使用该方法
  2. return new BigDecimal(Double.toString(val));
  3. }

四则运算

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 对象,用于表示使用的舍入策略。源代码如下所示:

  1. public BigDecimal divide(BigDecimal divisor, RoundingMode roundingMode) {
  2. return this.divide(divisor, scale, roundingMode.oldMode);
  3. }

从源码可以看出,商的 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 。如下例所示:

  1. boolean b1 = new BigDecimal("1").equals(new BigDecimal("1.0")); // false
  2. boolean b2 = new BigDecimal("1.0").equals(new BigDecimal("1.0")); // true

如果希望只比较两个对象表示的数值,可以使用 compareTo() 方法,如下例所示:

  1. int i1 = new BigDecimal("1").compareTo(new BigDecimal("1.0")); // true
  2. int i2 = new BigDecimal("1.0").compareTo(new BigDecimal("1.0")); // true