zoukankan      html  css  js  c++  java
  • Java-BigDecimal踩坑记录

    1.为什么要用BigDecimal?

    浮点数的计算过程中必然会造成精度丢失,BigDecimal丢失程度比float和double小。

    float和double是基本数据类型,而BigDecimal是封装类型。

    有得必有失,BigDecimal耗费时间和空间换取精度准确。

    2.初始化就存在精度问题

    double price = 18.899;
    BigDecimal a = BigDecimal.valueOf(price);
    BigDecimal b = new BigDecimal(price);
    System.out.println(a);//18.899
    System.out.println(b);//18.8990000000000009094947017729282379150390625
    System.out.println(new BigDecimal(0.85));//0.84999999999999997779553950749686919152736663818359375

    显然,建议用BigDecimal.valueOf()方法初始化

    3.使用除法divide的时候需要设置取整方式

            BigDecimal e = BigDecimal.valueOf(0.85);
            BigDecimal f = e.divide(BigDecimal.valueOf(0.85)).setScale(4,RoundingMode.DOWN);
            System.out.println(f);//1.0000
            e = BigDecimal.valueOf(1);
            f = e.divide(BigDecimal.valueOf(0.85),BigDecimal.ROUND_DOWN).setScale(4,RoundingMode.DOWN);//divide中不取整会报除不尽异常
            System.out.println(f);//1.0000
            e = BigDecimal.valueOf(1000);
            f = e.divide(BigDecimal.valueOf(0.85),BigDecimal.ROUND_DOWN).setScale(4,RoundingMode.DOWN);//但是取整放大倍数后发现精度也有损失
            System.out.println(f);//1176.0000
            e = BigDecimal.valueOf(1);
            f = NumberUtil.div(e,BigDecimal.valueOf(0.85));
            System.out.println(f);//1.1764705882 NumberUtil默认十位精度,减少精度损失

    4.遇到过这样一个例子,九折商品

    商品定价从别的系统获取,精确到两位小数

    折扣价 = 定价 * 0.9

    只有一个price字段用来存储价格,Java中使用BigDecimal,MySQL中使用decimal(10,2)存储。

    存的时候存折扣价,使用的时候需要折扣价和定价。

    使用折扣价的时候直接用price,使用定价的时候用折扣价去除以0.9。

    这样设计肯定是不合理的,这里看一下会造成什么问题。

    当商品定价为13.99的时候。(现在商品价格到两位小数也很正常)

            BigDecimal salePrice = BigDecimal.valueOf(13.99);
            BigDecimal rate = BigDecimal.valueOf(0.9);
    
            BigDecimal price = salePrice.multiply(rate).setScale(2, RoundingMode.DOWN);
            BigDecimal nowPrice = price.divide(rate,BigDecimal.ROUND_DOWN).setScale(2,RoundingMode.DOWN);
            System.out.println(price);//12.59
            System.out.println(nowPrice);//13.98
            //下面的代码没有问题,但是在数据库中是保存两位浮点数,因此数据库存取就是12.59,与上面的一样。
            price = NumberUtil.mul(salePrice,0.9);
            nowPrice = price.divide(rate,BigDecimal.ROUND_DOWN).setScale(2,RoundingMode.DOWN);
            System.out.println(price);//12.591
            System.out.println(nowPrice);//13.99

    这就导致了13.98和13.99的精度差的问题,涉及到钱都无小事,目前想到有3种解决方案

    (1)再开一列存价格,各玩各的

    (2)只存定价,折扣价的使用再去乘

    (3)只存折扣价,但是数据库精度取小数点后4位,计算折扣的时候精度不会丢失,除回来也不会丢失

     

    5.处理精度的参数RoundingMode roundingMode

    RoundingMode就是个枚举,BigDecimal.xxx,本质是数字0-7,代表精度处理方式

            // ROUND_UP() : 有多余的小数位进行进位处理,12.34
            System.out.println(BigDecimal.valueOf(12.333).setScale(2,0));
            // ROUND_DOWN() : 直接去掉多余的小数位,12.33
            System.out.println(BigDecimal.valueOf(12.333).setScale(2,1));
            // ROUND_CEILING(天花板) :正数进位向上,负数舍位向上。12.34 和 -12.33
            System.out.println(BigDecimal.valueOf(12.333).setScale(2,2));
            System.out.println(BigDecimal.valueOf(-12.333).setScale(2,2));
            // ROUND_FLOOR(地板) : 正数舍位向下,负数进位向下 12.33 和 -12.34
            System.out.println(BigDecimal.valueOf(12.333).setScale(2,3));
            System.out.println(BigDecimal.valueOf(-12.333).setScale(2,3));
            // ROUND_HALF_UP(四舍五入)
            System.out.println(BigDecimal.valueOf(12.334).setScale(2,4));
            System.out.println(BigDecimal.valueOf(12.335).setScale(2,4));
            // ROUND_HALF_DOWN(五舍六入)
            System.out.println(BigDecimal.valueOf(12.335).setScale(2,5));
            System.out.println(BigDecimal.valueOf(12.336).setScale(2,5));
            // ROUND_HALF_EVEN(银行家舍入,四舍六入五留双):
            // 这里“四”是指≤4时舍去,"六"是指≥6时进上。"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:5前为奇数,舍5入1;5前为偶数,舍5不进(0是偶数)。
            System.out.println(BigDecimal.valueOf(12.334).setScale(2,6));
            System.out.println(BigDecimal.valueOf(12.336).setScale(2,6));
            System.out.println(BigDecimal.valueOf(12.3351).setScale(2,6));
            System.out.println(BigDecimal.valueOf(12.3550).setScale(2,6));
            System.out.println(BigDecimal.valueOf(12.3450).setScale(2,6));
            // ROUND_UNNECESSARY(非必要舍入):断言请求的操作具有精确的结果,如果对非精确结果的操作指定此舍入模式,则抛出ArithmeticException。
            System.out.println(BigDecimal.valueOf(12.500).setScale(2,7));
            System.out.println(BigDecimal.valueOf(12.5001).setScale(2,7));//异常

    0和2,1和3没有试出差别......

     

    6.结论

    能不用除法就不用除法 

  • 相关阅读:
    E
    D
    C
    B
    Python
    IDEA 设置资源目录
    光猫指示灯含义
    IO模型
    Linux 总目录
    Linux python 使用
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/15154187.html
Copyright © 2011-2022 走看看