学会使用 BigDecimal
学会使用 BigDecimal
zyanBigDecimal
是 Java 提供的一个关于小数精确计算的类,其位于java.math
包下。
不同于基本数据类型,BigDecimal
是调用相关方法来进行运算。因为其拥有非常精确的小数计算能力,所以比较适合用于财务相关的计算等等。但是,其运行效率是不及 Float
与 Double
的,要按照实际情况,运用在合理的地方。
对象初始化
1 | BigDecimal bigDec = new BigDecimal("99.6"); |
或
1 | //不建议使用 |
强烈建议使用 String 类型进行初始化对象,强烈不推荐使用 Double 类型。原因何在?我们来看一个小测试。
执行下方代码
1 | BigDecimal bigDecimal=new BigDecimal("99.6"); |
运行结果
1 | 99.6 |
而执行下方代码
1 | BigDecimal bigDecimal=new BigDecimal(99.6); |
运行结果
1 | 99.599999999999994315658113919198513031005859375 |
问题立刻显现出来,使用 Double 型数据初始化 Bigdecimal 是不精确的,会损失精度。这不是 BigDecimal 的问题,而属于 Double 本身。在 IDEA 中,使用 Double 初始化时,则会“报黄”警告。==所以请务必使用 String 初始化 BigDecimal !==
建议的数据库解决方案
基于上一部分得到的结论,建议在数据库中使用 VARCHAR
类型或者 DECIMAL
,这里以 MySQL + MyBatis 为例。
数据库数据类型 | MyBatis 实体类数据类型 |
---|---|
VARCHAR | String |
DECIMAL | BigDecimal |
加减运算
加减运算比较简单,使用
add()
与subtract()
方法进行处理。
减法
示例代码
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
1 | 32.7 |
此处需要注意的是,==所有的运算方法在执行后并不会影响参与计算的数,他只会将计算结果返回!==
你可以使用新的对象来接受,或者覆盖旧的对象。
示例代码
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
加法
示例代码
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
1 | 166.5 |
乘除运算
乘法
使用 multiply()
方法进行处理。
示例代码
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
1 | 6663.24 |
除法
除法是比较特殊的一个运算,因为除数与被除数的关系,可能会造成结果为
无限循环小数
或无限不循环小数
,为了解决这一问题你需要指定小数保留方案
。
示例代码
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
1 | 33.2 |
因为该运算结果不是无限循环小数
,所以一切看起来都很 Nice ,但是当你稍稍改动一下,它就会“原形毕露”。
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result. at java.math.BigDecimal.divide(BigDecimal.java:1690) at ...
我是谁?我在哪?我写的是什么BUG?我们来看关键字Non-terminating decimal expansion
,翻译过来就是无穷小数扩张
。看到这里,你是否已经明白问题所在。也就是上边所提到的,需要指定小数保留方案
。
改动一下
1 | BigDecimal bigDecimal = new BigDecimal("99.6"); |
运行结果
1 | 1.48 |
此时一切都已恢复往日的平静,那么到底是什么力量遏制住了可恶的 Exception 呢?细心的同学会发现在 divide()
方法中多了两个参数,一个是2
,一个是BigDecimal.ROUND_DOWN
。其中,2是保留小数的位数,这个很容易理解。而BigDecimal.ROUND_DOWN
又是什么东西呢?他是 BigDecimal 类中的小数保留方案常量,算上这一个,一共有八个方案常量,请看解析。
常量 | 描述 |
---|---|
BigDecimal.ROUND_UP | 舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。 |
BigDecimal.ROUND_DOWN | 接近零的舍入模式。在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。 |
BigDecimal.ROUND_CEILING | 接近正无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同;如果为负,则舍入行为与 ROUND_DOWN 相同。 |
BigDecimal.ROUND_FLOOR | 接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同;如果为负,则舍入行为与 ROUND_UP 相同。 |
BigDecimal.ROUND_HALF_UP | 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。 |
BigDecimal.ROUND_HALF_DOWN | 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。 |
BigDecimal.ROUND_HALF_EVE | 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同;如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。此舍入模式也称为“银行家舍入法”,主要在美国使用。 |
BigDecimal.ROUND_UNNECESSARY | 断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。 |
这么多的小数保留方案,其实常用的也不多,感兴趣的话,可以自己写个测试类玩玩。
比较大小
比较大小也是需要调用方法的,使用
compareTo()
方法即可处理。
示例代码
1 | BigDecimal a = new BigDecimal("99.6"); |
运行结果
1 | 1 |
compareTo()
方法的返回值是一个int
型的数据,以上方示例为例,请看解析
情况 | compareTo() 的返回值 |
---|---|
a>b | 1 |
a=b | 0 |
a<b | -1 |
当你要判断一个数的与0的关系(正负)时可以这样写
1 | BigDecimal a = new BigDecimal("-99.6"); |
其他
去除无用的零
示例代码
1 | BigDecimal a = new BigDecimal("99.12300000"); |
运行结果
1 | 99.123 |