个人对类BigDecimal的一些见解

简介

Immutable, arbitrary-precision signed decimal numbers. A BigDecimal consists of an arbitrary precision integer unscaled value and a 32-bit integer scale. If zero or positive, the scale is the number of digits to the right of the decimal point. If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. The value of the number represented by the BigDecimal is therefore (unscaledValue × 10-scale).
  BigDecimal 由任意精度的整数非标度值 和32 位的整数标度 (scale) 组成。如果为零或正数,则标度是小数点后的位数。如果为负数,则将该数的非标度值乘以 10 的负scale 次幂。因此,BigDecimal表示的数值是(unscaledValue × 10-scale)。

构造方法

BigDecimal类共有16个左右的构造方法。不过最常用的还属下面两个:
BigDecimal(double val),以及BigDecimal(String val);
其中,当我们试图用第一个构造方法去实例化一个BigDecimal对象时,例如下述代码:

1
2
BigDecimal bd = new BigDecimal(0.1);
System.out.println(bd);

打印出来的值却出乎意料:0.1000000000000000055511151231257827021181583404541015625。JDK源码里面有这面一段解释:源码一共有三段解释,第一段是关于为什么double类型的构造方法得到的对象会不精确。出于上下文美观考虑,将源码注释中的长串字符移至上面,下方被替换为实例化对象引用bd。此外,译文来自于网络,具体见“参考文档”模块。

The results of this constructor can be somewhat unpredictable.One might assume that writing {@code new BigDecimal(0.1)} in Java creates a {@code BigDecimal} which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to {@code bd}. This is because 0.1 cannot be represented exactly as a {@code 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.
  参数类型为double的构造方法的结果有一定的不可预知性。有人可能认为在Java中写入newBigDecimal(0.1)所创建的BigDecimal正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于bd。这是因为0.1无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

第二段解释是关于参数为String的构造方法的:

The {@code String} constructor, on the other hand, is perfectly predictable: writing {@code new BigDecimal(“0.1”)} creates a {@code BigDecimal} which is exactly equal to 0.1, as one would expect.
Therefore, it is generally recommended that the {@linkplain #BigDecimal(String) String constructor} be used in preference to this one.
  另一方面,String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言,通常建议优先使用String构造方法。

第三段解释是关于参数为double结果不精确的解决方案:

When a {@code double} must be used as a source for a {@code BigDecimal}, note that this constructor provides an exact conversion; it does not give the same result as converting the {@code double} to a {@code String} using the {@link Double#toString(double)} method and then using the {@link #BigDecimal(String)} constructor. To get that result, use the {@code static} {@link #valueOf(double)} method.
  当double必须用作BigDecimal的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用Double.toString(double)方法,然后使用BigDecimal(String)构造方法,将double转换为String。要获取该结果,请使用static valueOf(double)方法。

方法divide()参数的理解

divide()的一个重载方法如下所示:

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

我想重点说的就是第二个参数roundingMode。

BigDecimal的“不可变”性

以源码中的valueOf()方法为例,解释何为不可变的。源码如下:

1
2
3
4
5
6
7
8
9
static BigDecimal valueOf(long unscaledVal, int scale, int prec) {
if (scale == 0 && unscaledVal >= 0 && unscaledVal < zeroThroughTen.length) {
return zeroThroughTen[(int) unscaledVal];
}
else if (unscaledVal == 0) {
return zeroValueOf(scale);
}
return new BigDecimal(unscaledVal == INFLATED ? INFLATED_BIGINT : null, unscaledVal, scale, prec);
}

在第8行中,返回值返回了一个新的BigDecimal对象。很显然,原对象的属性并没有发生任何变化。所以说BigDecimal类是不可变的。《Effective Java》中对不可变类的定义如下:

An immutable class is simply a class whose instances cannot be modified. All of the information contained in each instance is provided when it is created and is fixed for the lifetime of the object.
  不可变类只是其实例不能被修改的类,每个实例中包含的所有信息都必须在创建该实例的时候就提供,并在对象的整个生命周期内固定不变。

舍入方式RoundingMode

java中共有7种舍入方法,列举如下:

  1. ROUND_UP:远离零方向舍入,即向绝对值大的方向舍入,非零就进位。
  2. ROUND_DOWN:趋向零方向舍入。即向绝对值小的方向舍入,所有位都要舍弃。
  3. ROUND_CEILING:向正无穷方向舍入。若是正数,同ROUND_UP;若是负数,同ROUND_DOWN。
  4. ROUND_FLOOR:向负无穷方向舍入。若是正数,同ROUND_DOWN;若是负数,同ROUND_UP。
  5. HALF_UP:最近数字舍入(5入),这也是经典的四舍五入法。
  6. HALF_DOWN:最近数字舍入(5舍)。
  7. HALF_EVEN:银行家舍入法,四舍六入五取偶。即当x >= 6时,舍去该位的同时,前位进一;当x < 5时,直接舍去;当x = 5时,若前位为奇数则前位进一,若前位为偶数则直接舍去。

final与immutable的辩证关系

  1. 区别
    首先说下关键字final。final在Java中是一个保留的关键字,一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编译器会报编译错误。具体是指:对于final修饰的变量(包括成员变量、方法中的变量与代码块中的变量),一旦初始化后便不可再被更改(变量有基础数据类型和引用类型之分,对于前者而言,变量值一旦定义就不可再改变;而对于后者而言,是指这个变量不可再指向其他引用);对于final修饰的方法,表示该方法不可以在子类中被重写;对于final修饰的类,表示该类不可以被继承。
    下面说下immutable,即不可变类,它是相对于可变类而言的。所谓不可变类,是指当创建了这个类的实例后,就不允许修改它的属性值。
    事实上,被final关键字修饰对象的属性值是可以被修改的,而不可变类是不可以修改的。当你试图去修改时,它会返回一个新的对象给你。
  2. 联系
    final关键字是实现不可变类的基础。一个典型的不可变类的设计大概如下:第一步,向目标类添加修饰符final,保证其不被任何类继承;第二步,向目标类所有的成员变量添加private final修饰;只提供get方法,不提供set方法。

参考文档

  1. The Java API–BigDecimal
  2. jackiehff的CSDN博客
  3. java提高篇(三)—–java的四舍五入
坚持原创技术分享,您的支持将鼓励我继续创作!