8

阿里巴巴为什么建议使用BigDecimal进行浮点数运算 - 程序员xiaozhang

 1 year ago
source link: https://www.cnblogs.com/scott1102/p/17257865.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

阿里巴巴为什么建议使用BigDecimal进行浮点数运算

本文先引入一个例子,星期天你和女朋友去逛街,看到一家奶茶店。女朋友想喝奶茶了,你就去买了杯奶茶,然后你问了一下价格。店员说奶茶0.9元一杯。然后你给了1元钱。这个时候你忽然问了一下女友。服务员该找我们多少钱呢?女友说你个小傻瓜当然是0.1元啊。作为一个”严谨“的程序猿,这时你拿起电脑写了个简单计算如下:

2591839-20230326110931586-449507425.png

看到计算结果不是0.1这个时候你有点慌了,女朋友怎么会错呢?但是你没有犹豫女朋友说的怎么会错呢,女朋友说的一定是对的。一定是电脑计算错了,然后你毫不犹豫把电脑扔了。说了一句对对对该找0.1元。【PS奶茶目前当然不可能1元钱,本文为了演示计算效果所以定义奶茶1元,因为不是所有的浮点运算都会有这样的问题】。

千万别小看这些一点点的计算误差,假如我说假如全国人民一人送你0.1元你能实现财富自由了【呸,别做白日梦了好好搬砖去吧】

好了聊文章的主题BigDecimal这个知识点,这个知识点应该很多人都知道了,我感觉很有有用就聊一下。阿里巴巴开发手册中提到:

使用BigDecimal来定义值,再进行浮点数的运算操作。

注意:BigDecimal(double)存在精度损失的风险,在精确计算或比较的场景中可能会导致业务逻辑异常。如:BigDecimal g = new BigDecimal(0.1f)。实际存储的值为:0.10000000149011611938 。

      float a = 1.0f -0.9f;
        float b = 0.9f-0.8f ;
        System.out.println(a);
        System.out.println(b);
        System.out.println(a==b);
2591839-20230326111215635-1086984013.png

为什么浮点数 float 或 double 运算的时候会有精度丢失的风险呢?

这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。

那怎么解决精度丢失的问题呢?那就是本文要说的BigDecimal。用它可以实现对浮点的计算,解决精度丢失的问题。如下用差不多相同的代码结果就是对的【PS一定要记住女朋友说的就是对的就是0.1】。

2591839-20230326111321865-1009831575.png

BigDecimal创建方法,请先看注意事项:

阿里官方:【强制】禁止使用构造方法BigDecimal(double)的方式把double值转换为BigDecimal对象

正确的创建方法:优先推荐入参为String的构造方法,或者使用BigDecimal的valueOf方法,此方法内部其实执行了Double的toString。而Double的toString按double实际能表达的精度对尾数进行了拦截。如下推荐使用:

BigDecimal a = new BigDecimal("0.1"); 

BigDecimal  b = BigDecimal.valueOf(0.1);

开发中用到计算也就是加减乘除,BigDecimal中相关方法如下:

add 方法用于将两个 BigDecimal 对象相加。subtract 方法用于将两个 BigDecimal 对象相减。multiply 方法用于将两个 BigDecimal 对象相乘,divide 方法用于将两个 BigDecimal 对象相除。

2591839-20230326111552509-1487235338.png

这里需要注意的是,在我们使用 divide 方法的时候尽量使用 3 个参数方法。其中 scale 表示要保留几位小数,roundingMode 代表保留规则。具体可以看源码,源码中介绍的很清楚了,并且给了各种例子如下:

2591839-20230326111650465-450022091.png

大小的比较

a.compareTo(b) : 返回 -1 说明 a 小于 b,0 说明a 等于 b , 1 说明 a 大于 b。用法如下:

2591839-20230326111741933-1562143761.jpg

注意:BigDecimal的比较不能使用equals进行比较【原因还是因为精度的问题】,一定要使用compareTo比较,如下:

2591839-20230326111834187-1944765585.png

BigDecilmal计算相关的工具类。

add方法:

public static BigDecimal add(Number v1, Number v2) {
        return add(new Number[]{v1, v2});
    }
    public static BigDecimal add(Number... values) {
        if (ArrayUtil.isEmpty(values)) {
            return BigDecimal.ZERO;
        }
        Number value = values[0];
        BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
        for (int i = 1; i < values.length; i++) {
            value = values[i];
            if (null != value) {
                result = result.add(new BigDecimal(value.toString()));
            }
        }
        return result;
    }

substract方法:

public static BigDecimal sub(Number v1, Number v2) {
        return sub(new Number[]{v1, v2});
    }
    public static BigDecimal sub(Number... values) {
        if (ArrayUtil.isEmpty(values)) {
            return BigDecimal.ZERO;
        }
        Number value = values[0];
        BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
        for (int i = 1; i < values.length; i++) {
            value = values[i];
            if (null != value) {
                result = result.subtract(new BigDecimal(value.toString()));
            }
        }
        return result;
    }

multiply乘法:

public static BigDecimal mul(Number v1, Number v2) {
        return mul(new Number[]{v1, v2});
    }
    public static BigDecimal mul(Number... values) {
        if (ArrayUtil.isEmpty(values)) {
            return BigDecimal.ZERO;
        }
        Number value = values[0];
        BigDecimal result = new BigDecimal(null == value ? "0" : value.toString());
        for (int i = 1; i < values.length; i++) {
            value = values[i];
            result = result.multiply(new BigDecimal(null == value ? "0" : value.toString()));
        }
        return result;
    }

divide 除法:

public static BigDecimal div(BigDecimal v1, BigDecimal v2,Integer scale,RoundingMode roundingMode) {
        if (null == v1) {
            return BigDecimal.ZERO;
        }
        if (scale < 0) {
            scale = -scale;
        }
        return v1.divide(v2, scale, roundingMode);
    }

谢谢点赞,也欢迎你分享给其他的开发者,让更多的人知道。当然也欢迎未关注的小伙伴关注。

2591839-20230326112204826-1445140343.jpg

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK