浮点数用二进制进行计算时,会出现计算不精准的情况。比如,0.3-0.2 ,我们预想的结果是 0.1,可最后得到的结果是 0.09999999999999998,这跟二进制表示小数的方式有关。所以为了求得精确的数值,一般采用 BigDecimal 工具类计算小数。
在银行、帐户、计费等商业领域,BigDecimal 是必须引入的工具类。
我们在使用 Java 中的高精度浮点数时,有时也会根据实际需求需要对小数的舍入模式进行改变(默认为四舍五入方式,ROUND_HALF_UP)。

什么是舍弃部分(舍弃位)

比如 1.55,保留到小数后点 1 位,那么第二个 5 及之后都是舍弃部分,第一个 5 是进位还是保持原值需要根据舍入模式来确定。

round 的原意是一个整数,通常是 0 或 5。所以在四舍五入的时候,会把 5 作为一个中间点,舍弃该位,前一位加 1,也就是向上舍入。

下面对 8 种舍入模式进行介绍。

ROUND_UP

丢弃舍弃部分,并在舍弃部分前一位加 1,向上舍入。
比如 1.54 ,保留到小数点后 1 位,不考虑第二位及以后(舍弃部分)的值为多少。

  1. BigDecimal.valueOf(1.54).setScale(1,BigDecimal.ROUND_UP).toString();

结果:

  1. 1.6

ROUND_DOWN

丢弃舍弃部分,舍弃部分前一位不变。
比如 1.56 ,保留到小数点后 1 位,不考虑第二位及以后(舍弃部分)的值为多少。

  1. BigDecimal.valueOf(1.56).setScale(1,BigDecimal.ROUND_DOWN).toString();

结果:

  1. 1.5

ROUND_CEILING

如果数字为正,则舍入行为与 ROUND_UP 相同,如果为负数,则舍入行为与 ROUND_DOWN 相同。

此舍入模式始终不会减少计算值。

  1. BigDecimal.valueOf(1.54).setScale(1, RoundingMode.CEILING)
  2. BigDecimal.valueOf(-1.56).setScale(1, RoundingMode.CEILING)

结果:

  1. 1.6
  2. -1.5

ROUND_FLOOR

如果数字为正,则舍入行为与 ROUND_DOWN 相同,如果为负数,则舍入行为与 ROUND_UP 相同。

此舍入模式始终不会增加计算值。

  1. BigDecimal.valueOf(1.57).setScale(1, RoundingMode.FLOOR);
  2. BigDecimal.valueOf(-1.54).setScale(1, RoundingMode.FLOOR);

结果:

  1. 1.5
  2. -1.6

ROUND_HALF_UP

向最接近的数字舍入,如果舍弃部分 >= 5,向上舍入,如果舍弃部分 < 5,向下舍入。
比如 1.54,保留到小数点后 1 位,小数点第二位(舍弃部分)是 4 ,舍弃部分小于 5,向下舍入。
1.55 小数点第二位是 5,在 [0,9] 这个区间更接近于 9,所以舍弃部分前一位加 1。

这就是我们以前熟知的四舍五入原则。

  1. BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_UP);
  2. BigDecimal.valueOf(1.54).setScale(1, RoundingMode.HALF_UP);

结果:

  1. 1.6
  2. 1.5

ROUND_HALF_DOWN

向最接近的数字舍入,如果舍弃部分 >= 6,向上舍入,如果舍弃部分 < 6,向下舍入。这可以说是“五舍六入”原则。

  1. BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_DOWN);
  2. BigDecimal.valueOf(1.55).setScale(1, RoundingMode.HALF_DOWN);

结果:

  1. 1.6
  2. 1.5

ROUND_HALF_EVEN

这种舍入模式就是“银行家舍入”,所谓银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。
其规则是:
当舍去位的数值小于 5 时,直接舍去该位;
当舍去位的数值大于等于 6 时,在舍去该位的同时向前位进一;
当舍去位的数值等于 5 且(5 后不为空且非全 0)时,在舍去该位的同时向前位进一;
当舍去的数值等于 5 且(5 后为空或全 0)时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。

  1. BigDecimal.valueOf(1.54).setScale(1, RoundingMode.HALF_EVEN);
  2. BigDecimal.valueOf(1.56).setScale(1, RoundingMode.HALF_EVEN);
  3. BigDecimal.valueOf(1.554).setScale(1, RoundingMode.HALF_EVEN);
  4. BigDecimal.valueOf(1.654).setScale(1, RoundingMode.HALF_EVEN);
  5. BigDecimal.valueOf(1.55).setScale(1, RoundingMode.HALF_EVEN);
  6. BigDecimal.valueOf(1.45).setScale(1, RoundingMode.HALF_EVEN);

结果:

  1. 1.5
  2. 1.6
  3. 1.6
  4. 1.7
  5. 1.6
  6. 1.4

ROUND_UNNECESSARY

断言请求的操作具有精确的结果,因此不需要舍入。
如果对获得精确结果的操作指定此舍入模式,则抛出 ArithmeticException。

  1. BigDecimal.valueOf(0.3-.02).setScale(1, RoundingMode.UNNECESSARY);

结果:

  1. Exception in thread "main" java.lang.ArithmeticException: Rounding necessary