浮点数用二进制进行计算时,会出现计算不精准的情况。比如,0.3-0.2
,我们预想的结果是 0.1,可最后得到的结果是 0.09999999999999998
,这跟二进制表示小数的方式有关。所以为了求得精确的数值,一般采用 BigDecimal 工具类计算小数。
在银行、帐户、计费等商业领域,BigDecimal 是必须引入的工具类。
我们在使用 Java 中的高精度浮点数时,有时也会根据实际需求需要对小数的舍入模式进行改变(默认为四舍五入方式,ROUND_HALF_UP
)。
什么是舍弃部分(舍弃位)
比如 1.55
,保留到小数后点 1 位,那么第二个 5 及之后都是舍弃部分,第一个 5 是进位还是保持原值需要根据舍入模式来确定。
round 的原意是一个整数,通常是 0 或 5。所以在四舍五入的时候,会把 5 作为一个中间点,舍弃该位,前一位加 1,也就是向上舍入。
ROUND_UP
丢弃舍弃部分,并在舍弃部分前一位加 1,向上舍入。
比如 1.54
,保留到小数点后 1 位,不考虑第二位及以后(舍弃部分)的值为多少。
BigDecimal.valueOf(1.54).setScale(1,BigDecimal.ROUND_UP).toString();
结果:
1.6
ROUND_DOWN
丢弃舍弃部分,舍弃部分前一位不变。
比如 1.56
,保留到小数点后 1 位,不考虑第二位及以后(舍弃部分)的值为多少。
BigDecimal.valueOf(1.56).setScale(1,BigDecimal.ROUND_DOWN).toString();
结果:
1.5
ROUND_CEILING
如果数字为正,则舍入行为与 ROUND_UP 相同,如果为负数,则舍入行为与 ROUND_DOWN 相同。
此舍入模式始终不会减少计算值。
BigDecimal.valueOf(1.54).setScale(1, RoundingMode.CEILING)
BigDecimal.valueOf(-1.56).setScale(1, RoundingMode.CEILING)
结果:
1.6
-1.5
ROUND_FLOOR
如果数字为正,则舍入行为与 ROUND_DOWN 相同,如果为负数,则舍入行为与 ROUND_UP 相同。
此舍入模式始终不会增加计算值。
BigDecimal.valueOf(1.57).setScale(1, RoundingMode.FLOOR);
BigDecimal.valueOf(-1.54).setScale(1, RoundingMode.FLOOR);
结果:
1.5
-1.6
ROUND_HALF_UP
向最接近的数字舍入,如果舍弃部分 >= 5,向上舍入,如果舍弃部分 < 5,向下舍入。
比如 1.54
,保留到小数点后 1 位,小数点第二位(舍弃部分)是 4 ,舍弃部分小于 5,向下舍入。1.55
小数点第二位是 5,在 [0,9]
这个区间更接近于 9,所以舍弃部分前一位加 1。
这就是我们以前熟知的四舍五入原则。
BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_UP);
BigDecimal.valueOf(1.54).setScale(1, RoundingMode.HALF_UP);
结果:
1.6
1.5
ROUND_HALF_DOWN
向最接近的数字舍入,如果舍弃部分 >= 6,向上舍入,如果舍弃部分 < 6,向下舍入。这可以说是“五舍六入”原则。
BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_DOWN);
BigDecimal.valueOf(1.55).setScale(1, RoundingMode.HALF_DOWN);
结果:
1.6
1.5
ROUND_HALF_EVEN
这种舍入模式就是“银行家舍入”,所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。
其规则是:
当舍去位的数值小于 5 时,直接舍去该位;
当舍去位的数值大于等于 6 时,在舍去该位的同时向前位进一;
当舍去位的数值等于 5 且(5 后不为空且非全 0)时,在舍去该位的同时向前位进一;
当舍去的数值等于 5 且(5 后为空或全 0)时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。
BigDecimal.valueOf(1.54).setScale(1, RoundingMode.HALF_EVEN);
BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_EVEN);
BigDecimal.valueOf(1.554).setScale(1, RoundingMode.HALF_EVEN);
BigDecimal.valueOf(1.654).setScale(1, RoundingMode.HALF_EVEN);
BigDecimal.valueOf(1.55).setScale(1, RoundingMode.HALF_EVEN);
BigDecimal.valueOf(1.45).setScale(1, RoundingMode.HALF_EVEN);
结果:
1.5
1.6
1.6
1.7
1.6
1.4
ROUND_UNNECESSARY
断言请求的操作具有精确的结果,因此不需要舍入。
如果对获得精确结果的操作指定此舍入模式,则抛出 ArithmeticException。
BigDecimal.valueOf(0.3-.02).setScale(1, RoundingMode.UNNECESSARY);
结果:
Exception in thread "main" java.lang.ArithmeticException: Rounding necessary