分享BigDecimal金额计算的4种方式

小技巧
placeholder image
admin 发布于:2023-04-22 20:03:58
阅读:loading

基本介绍

金额计算这块通常都是基于2位小数的四舍五入,如果是自己的系统内部功能金额位数一般都是固定的,在正常的加减乘除运算逻辑都是保留同样位数的,但是乘法和除法相对比较特殊,在计算小数的部分可能会计算出更多位数的小数点,如若需要将计算后的金额数据与某个值进行对比时,就需要考虑数据部分的完全相等了,在应对此种情况的数据逻辑时常见的有两种,第一种是两个数值相见,取绝对值的误差阈值,若在允许的阈值范围内(比如两个数值都是最多2位小数,阈值设置为0.0001),则认为它们是相等的;第二种则是需要知晓该金额在计算过程中所使用的计算方式,采用统一的计算方式计算得出同样的结果即可。

去年实现的一个功能中正好就使用到了多种小数位数的计算方式,大概是4种:四舍五入、截位法、四舍六入五成双、正舍负入(实际还有更多种),这些数据的计算都可以有相关的代码计算方式得出,本次主要分享Math和BigDecimal,参考如下:

Math

@RunWith(JUnit4.class)
public class CalculateMathTest {

    /**
     * 保留2位小数,四舍五入
     */
    @Test
    public void round() {
        System.out.println(Math.round(12345.554 * 100) / 100D);
        System.out.println(Math.round(12345.555 * 100) / 100D);
        System.out.println(Math.round(12345.556 * 100) / 100D);
    }

    /**
     * 保留2位小数,正舍负入(ceil:反过来,正入负舍)
     */
    @Test
    public void floor() {
        System.out.println(Math.floor(123.554 * 100) / 100D);
        System.out.println(Math.floor(123.555 * 100) / 100D);
        System.out.println(Math.floor(123.556 * 100) / 100D);

        System.out.println(Math.floor(-123.554 * 100) / 100D);
        System.out.println(Math.floor(-123.555 * 100) / 100D);
        System.out.println(Math.floor(-123.556 * 100) / 100D);
    }

}

尝试着写了点小demo,发现Math这个数学函数类提供的API重点并不在于金额的计算上,所以找到的金额计算API比较少,在金额计算这块比较鸡肋(先乘以100再除以100得到带小数位数的金额),也算是满足了本次4种金额计算方式中的两种了。

BigDecimal

/**
 * 保留2位小数,四舍五入
 */
@Test
public void round() {
    System.out.println(new BigDecimal("12345.554").setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println(new BigDecimal("12345.555").setScale(2, BigDecimal.ROUND_HALF_UP));
    System.out.println(new BigDecimal("12345.556").setScale(2, BigDecimal.ROUND_HALF_UP));
}
/**
 * 保留2位小数,正舍负入
 */
@Test
public void floor() {
    System.out.println(new BigDecimal("12345.554").setScale(2, BigDecimal.ROUND_FLOOR));
    System.out.println(new BigDecimal("12345.555").setScale(2, BigDecimal.ROUND_FLOOR));
    System.out.println(new BigDecimal("12345.556").setScale(2, BigDecimal.ROUND_FLOOR));
    System.out.println();
    System.out.println(new BigDecimal("-12345.554").setScale(2, BigDecimal.ROUND_FLOOR));
    System.out.println(new BigDecimal("-12345.555").setScale(2, BigDecimal.ROUND_FLOOR));
    System.out.println(new BigDecimal("-12345.556").setScale(2, BigDecimal.ROUND_FLOOR));
}
/**
 * 保留2位小数,截位法
 */
@Test
public void down() {
    System.out.println(new BigDecimal("12345.554").setScale(2, BigDecimal.ROUND_DOWN));
    System.out.println(new BigDecimal("12345.555").setScale(2, BigDecimal.ROUND_DOWN));
    System.out.println(new BigDecimal("12345.556").setScale(2, BigDecimal.ROUND_DOWN));
}
/**
 * 保留2位小数,四舍六入五成双
 */
@Test
public void even() {
    System.out.println(new BigDecimal("12345.234").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.236").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.235").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.245").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println();
    System.out.println(new BigDecimal("12345.2450").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2451").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2452").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2453").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2454").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2455").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2456").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2457").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2458").setScale(2, BigDecimal.ROUND_HALF_EVEN));
    System.out.println(new BigDecimal("12345.2459").setScale(2, BigDecimal.ROUND_HALF_EVEN));
}

其它说明

BigDecimal中提供了多种金额计算的方法,在RoundingMode类中也单独声明(封装)了一些运算规则的实现,四舍六入五成双又称银行间算法,如果跨界了还是不太容易理解的,本次实践呢又拿出来了多个示例输出来理解,所以我整理的知识点为:

(1)保留两位小数,所以至少应该精确到3位小数,根据第三位小数的值来判断如何第2位小数位来进位;

(2)小数点第3位如果是4则舍去,如果是6则进1;

(3)小数点第3位如果是5,且无第4位小数,则观察第2位小数,若为基数则进1,若为偶数则舍去;

(4)小数点第3位如果是5,且存在第4位小数,则观察第4位小数,若为0则舍去,否则则进1;

所以,这里的四舍六入五成双的输出结果为:

image.png


 点赞


 发表评论

当前回复:作者

 评论列表


留言区