在计算机科学中,精确性是至关重要的。小数点计算、精度精确度,以及浮点误差,这些是所有程序员都已经很熟悉的数学概念。虽然大多数程序都可以运作普遍的数字储存类型如int、float、以及double,但是他们并非总是能满足所有的需求。Java中的BigDecimal类就是为了应对这样的情况而特别设计的一种高精度浮点数计算类。
本文将介绍Java中的BigDecimal类及其使用,当你需要精确计算时,这个类会提供动力。“大数”(Decimal)就是指能表示任意精度大小的数字,所以它完全可以处理虽然非常大却仍然需要精确计算的数字或者货币。BigDecimal已经被广泛地应用到财务类程序和科学计算领域中。避免float和double类型的精度丢失以及问题的同时,也为我们提供了更高的精确度和更加灵活的运算。
## BigDecimal基础
BigDecimal类是Java中的一个强大的数学类。它不仅仅可以表示基本数据类型中已有的数字类型,还可以支持更高的精度。BigDecimal实例可以处理被我们熟知的所有数字类型,如int、float、double、甚至可以处理大到几千个位数的高精度数字。
在BigDecimal类的创建中,我们可以传入字符串类型的数字,也可以通过调用方法来处理数字。由于就算大到数千位数的数字,字符串的处理通常也不会成为性能的瓶颈,因此这里的字符串储存方式并不是很影响性能。
在BigDecimal的基础中,我们将讨论的是最基础的BigDecimal类型,以及怎样使用它进行数学运算。
### 创建BigDecimal
创建一个BigDecimal与创建任何其他的对象是一样的。它可以使用new 关键字来创建,如下所示。
```java
BigDecimal a = new BigDecimal(10.5);
```
但是上述方式通常并不是最好的选择,因为在创建的时候,BigDecimal类并不知道你要的具体精度精度大小。在这个例子中,BigDecimal实例在创建时,将会保留浮点数的原精度。如果你创建的时候,同时也在使用格式化,则会对操作的性能产生负面影响。
有两种常用方法来创建BigDecimal对象。首先,你可以传入字符串数量,会将它作为一个文本形式的数字来创建BigDecimal对象。这种方式可以保证你的精度和位数不会出现问题。
```java
BigDecimal b = new BigDecimal("10.5");
```
你也可以使用doubleValue() 方法、floatValue()方法、longValue()方法等为基础的数据类型创建BigDecimal对象。这种方法在内部会被转换成字符串储存形式,所以并不会受限于基础数据类型的精度。
### BigDecimal的等值比较
在使用BigDecimal时,我们经常需要进行等值比较。在这方面,这个类中有几个非常有用的方法你需要知道。
firstly,setScale() 方法可以设置BigDecimal的精度。(refer to the math projects from high school)它有两个参数:第一个参数是表示精度的数字,第二个参数是表示舍入方式的RoundingMode。setScale() 方法的返回值是一个新的BigDecimal,其精度已经被设置为你提供的值。
```java
BigDecimal x = new BigDecimal(10.5);
BigDecimal y = x.setScale(4, RoundingMode.HALF_EVEN);
// y 现在的是精确小数点后四位的 10.5000
```
isZero()方法用来测试BigDecimal是否等于0。如果一个BigDecimal为零,它将返回true:
```java
BigDecimal zero = new BigDecimal("0");
System.out.println(zero.isZero()); //true
```
能够用于比较两个BigDecimal值的方法还有compareTo()方,它将返回一个表示两个BigDecimal数值大小关系的值。如果左边的数多于右边的数,那么返回值将是一个正数。如果左边的数小于右边的数,那么返回一个负数。如果两个数是相等的,那么方法将会返回0。
### BigDecimal的基本计算操作
有5个基本的算术方法在BigDecimal中可以使用,add()、subtract()、multiply()、divide()、和remainder()来代表加、减、乘、除和取模。通过这些方法,我们可以对BigDecimal进行任何计算。
add()、subtract()、和 multiply() 各自都只接受一个(BigDecimal)参数。这些方法的返回值是新的 BigDecimal,表示操作的结果。在 divide() 方法中,你可以为其传入一个控制精度的值,就可以进行除法操作。然而,它在处理无限小数时是一件困难的事情,就连double和float的除法表现也不太好。因此,额外提供了多个重载方法,可以为除法操作指定精度,以及舍入方式的scaRoundingMode。
```java
BigDecimal q1 = new BigDecimal("10.5");
BigDecimal q2 = new BigDecimal("5");
BigDecimal q3 = q1.add(q2);
BigDecimal q4 = q1.subtract(q2);
BigDecimal q5 = q1.multiply(q2);
BigDecimal q6 = q1.divide(q2);
```
使用remainder()方法,可以计算一个BigDecimal和它除以给定BigDecimal进行的整数余数运算,并返回余数。
```java
BigDecimal a = new BigDecimal("20.5");
BigDecimal b = new BigDecimal("3");
BigDecimal result = a.remainder(b);
System.out.println(result); // 2.5
```
为了能够让BigDecimal类能够更加高效地工作,它具有内置的缓存池,其中包含大量的预创建的BigDecimal实例。这些实例在使用的时候是很快的。
### rounding的使用
在BigDecimal类中,rounding的使用是非常重要的。默认情况下,在BigDecimal的除法操作中,结果会按照HALF_UP的舍入方式进行调整。在需要更高的精度时,这种默认的舍入方式未必总是最合适的。
在除法操作中,ROUND_DOWN舍入方式通常是将计算结果完全截断,不做额外的处理。但是它在处理无限小数和控制小数位数中通常也被广泛使用。
在舍入操作中,ROUND_HALF_UP是广受欢迎的一种舍入方式,这种方式会向最接近的整数舍入,并且如果两个数字距离相等,那么它将向“最接近参数的上面的整数”方向舍入。这种舍入方式的权衡是能够处理更为复杂的舍入问题,但是当更高的精度需要时,它的运算时间较长且不高效。
## BigDecimal中的注意事项
BigDecimal可能会有些棘手,因为它们总会计算一些奇怪的细节。例如,如果你在BigDecimal中除以一个非常小的数,结果可能会超出你的期望。 抛出ArithmeticException的异常,来表示一些运算错误的发生。
另外还要注意一个重要的问题:使用BigDecimal时,确保你的工作单位是为财务模块或者科学计算模块而设计的。它们不能简单地代替基础数据类型,而应该足够小心谨慎地使用,以避免性能和开销问题。
* 据此我们推荐,如果需要存储和计算小数点后若干位以上的数据时,请考虑使用BigDecimal。
* 尽管BigDecimal算法需要为计算扫描比基础数据类型繁琐,但如果这样的需求在程序中十分必要时,这些额外的工作将会受到极大的欢迎。
* Java开发人员会经常用到BigDecimal,因为它可以确保在低精度下保证完美的算法。即使在高精度计算上也可以接囗轻松处理,同时它灵活性也使它成为一个理想的第三方工具的选择。
总而言之,BigDecimal是一个以高精度运算为主的Java类。它被广泛使用在财务和科学计算领域,使得Java前端开发可以舒适地进行浮点数计算。当在高精度小数点下普通算法不能胜任的情况下,BigDecimal将提供你最需的帮助。