当前位置:首页 > 科技  > 软件

不掌握 BigDecimal 的四大坑你敢用吗?

来源: 责编: 时间:2024-05-30 17:18:01 314观看
导读BigDecimal 是 Java 中的一个类,这个相信大家都是知道的。它的作用就是可以表示任意精度的十进制数,BigDecimal 提供了精确的数字运算,适用于需要高精度计算的场景,例如金融、货币或者税收等涉及到金钱的地方。与 double

BigDecimal 是 Java 中的一个类,这个相信大家都是知道的。它的作用就是可以表示任意精度的十进制数,BigDecimal 提供了精确的数字运算,适用于需要高精度计算的场景,例如金融、货币或者税收等涉及到金钱的地方。dyV28资讯网——每日最新资讯28at.com

与 double 和 float 不同的是,BigDecimal 对象在计算的过程中不会丢失精度,那么下面我们就来看下第一个坑,浮点精度的坑。dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

一、浮点精度的坑

我们先来看一个例子:dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal num1 = new BigDecimal("0.1");        BigDecimal num2 = new BigDecimal("0.10");        // false        System.out.println(num1.equals(num2));        // 0        System.out.println(num1.compareTo(num2));    }

compareTo 方法比较中,a.compareTo(b)dyV28资讯网——每日最新资讯28at.com

返回:dyV28资讯网——每日最新资讯28at.com

  • -1: a小于b
  • 0: a等于b
  • 1: a大于b。

在上方的代码中,我们使用 new BigDecimal 的形式 new 了两个 BigDecimal 对象,分别是 0.1 和0.10。dyV28资讯网——每日最新资讯28at.com

我们分别使用了 equals 与 compareTo 进行比较,当使用 equals 进行比较时,返回了 false,这是因为 equals 不仅比较了值是否相等,还比较了精度是否相等,源码中是这样写的:dyV28资讯网——每日最新资讯28at.com

 public boolean equals(Object x) {        if (!(x instanceof BigDecimal))            return false;        BigDecimal xDec = (BigDecimal) x;        if (x == this)            return true;        if (scale != xDec.scale)            return false;        long s = this.intCompact;        long xs = xDec.intCompact;        if (s != INFLATED) {            if (xs == INFLATED)                xs = compactValFor(xDec.intVal);            return xs == s;        } else if (xs != INFLATED)            return xs == compactValFor(this.intVal);        return this.inflated().equals(xDec.inflated());    }

所以在使用 equals 进行比较两个 BigDecimal 的大小时,一定要注意这一点了。dyV28资讯网——每日最新资讯28at.com

简单概括一下,如果比较两个 BigDecimal 对象的大小,那就使用 compareTo 方法;如果严格比较精度的大小,那就使用 equals 方法进行比较。dyV28资讯网——每日最新资讯28at.com

上面我们知道了如何比较两个 BigDecimal 对象的大小,equals 比较的还有他们的精度,那么精度又是如何设置的呢,这块有没有坑呢?dyV28资讯网——每日最新资讯28at.com

二、设置精度的坑

有的同学可能会说了,设置精度还有啥坑啊,设置了精度就好了吗,哎对,就是这个意思,在做 BigDecimal 对象计算的时候,一定要设置精度。相反,有的同学就不喜欢设置精度,那么这 BUG 不就来了吗。dyV28资讯网——每日最新资讯28at.com

来看一个例子:dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal num1 = new BigDecimal("1");        BigDecimal num2 = new BigDecimal("3");        BigDecimal result = num1.divide(num2); // 默认舍入模式为 UNNECESSARY,会抛出 ArithmeticException    }

上述的代码在执行结束之后会报错 ArithmeticException ,这是因为默认舍入模式为 UNNECESSARY,所以会抛出 ArithmeticException。dyV28资讯网——每日最新资讯28at.com

Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

要解决这个异常也很容易,只需要加上精度即可。dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal num1 = new BigDecimal("1");        BigDecimal num2 = new BigDecimal("3");        BigDecimal result = num1.divide(num2, 2,RoundingMode.HALF_UP);        // 输出:0.33        System.out.println(result);    }

那么出现这个异常的原因是什么你考虑过吗?为什么加了精度就不报错了呢?dyV28资讯网——每日最新资讯28at.com

这个异常在源码中也有说明:dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

大概意思就是如果在做 divide 运算时,如果商是一个无限小数,而操作的结果是一个精确的数字,那么就会抛出该异常。dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

不知道大家注意到一点没有,就是上面做除法运算的时候,也就是 BigDecimal result = num1.divide(num2, 2,RoundingMode.HALF_UP); 这行代码的位置,使用了一个新的变量 result 来接收结果值,因为 BigDecimal 是不可变的,因此每次进行运算都会创建一个新的 BigDecimal 对象,所以这一点也是需要注意的,创建的多了可能会产生大量的垃圾对象。dyV28资讯网——每日最新资讯28at.com

讲完了精度与运算,那么你初始化的方式对吗?dyV28资讯网——每日最新资讯28at.com

三、初始化的坑

先来看代码:dyV28资讯网——每日最新资讯28at.com

BigDecimal num = new BigDecimal(0.1); // 使用双精度浮点数构造System.out.println(num); // 输出: 0.1000000000000000055511151231257827021181583404541015625BigDecimal num2 = new BigDecimal("0.1"); // 使用字符串构造System.out.println(num2); // 输出: 0.1

在使用 new BigDecimal 构造器进行初始化的时候,如果有初始值,最好使用字符串的构造方法进行初始化。dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

在使用 double 的构造器进行新建时,本身传入的 0.1 就是浮点类型了,为了不丢失精度,在使用 new BigDecimal 新建时就把这个近似值完整的保留下来了。dyV28资讯网——每日最新资讯28at.com

或者就是 另外一种初始化方式 BigDecimal.valueOf(0.1);,通过看源码可以发现,在 valueOf 的内部,将 Double 类型直接转为了字符串了,因此也就不会存在精度丢失的问题了。dyV28资讯网——每日最新资讯28at.com

对于使用 new BigDecimal(0.1) 构造时,源码中也已经说明了这个问题。dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

大体意思就是生成的 BigDecimal 对象不是我们想要的 0.1,推荐使用 String 类型的构造方法。dyV28资讯网——每日最新资讯28at.com

dyV28资讯网——每日最新资讯28at.com

上面我们已经学会了如何初始化,如何运算,下一步就是如何用了,例如转字符串,很多同学可能会说,转字符串 toString() 不就好了,如果你也这样想,那你单纯了弟弟。dyV28资讯网——每日最新资讯28at.com

四、转字符串的坑

还是先看一段代码:dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal a = BigDecimal.valueOf(89382389312389594.33822312317952678768725);        System.out.println(a.toString()); // 输出:8.93823893123896E+16        String str = a.setScale(2, RoundingMode.HALF_UP).toString();        System.out.println(str); // 输出: 89382389312389600.00    }

上面代码中是一个非常大的数,我想把他转为字符串,可是在使用 toString() 方法时,打印出来的却是科学计数法。dyV28资讯网——每日最新资讯28at.com

所以如果想使用 toString() 方法进行转字符串时,可以使用设置精度的方法,但是结果还是与我们的预期有所差别,我们想要的是一模一样的打印出来呢?dyV28资讯网——每日最新资讯28at.com

那么 toPlainString 就上场了,这个方法返回一个字符串的表示形式,包含所有的有效数字。dyV28资讯网——每日最新资讯28at.com

代码修改如下:dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal a = BigDecimal.valueOf(89382389312389594.99933822312317952678768725);        System.out.println(a.toPlainString());    }

修改之后就可以了吗,不可以,忘了上面说的吗,使用 String 的构造函数吧兄弟,double 类型的构造函数会丢失精度的。dyV28资讯网——每日最新资讯28at.com

最终代码如下:dyV28资讯网——每日最新资讯28at.com

    public static void main(String[] args) {        BigDecimal a = new BigDecimal("89382389312389594.99933822312317952678768725");        System.out.println(a.toPlainString());    }

除了上述两种转字符串的方法外,还有一种,就是 toEngineeringString,这个方法也是返回一个字符串,包含有效数字,但是它会使用工程计数法,科学计数法的一种变体,它使用数字的倍数来表示值,使得指数是 3 的倍数。例如,1000会显示为"1E3",而不是"1E+3"。dyV28资讯网——每日最新资讯28at.com

所以总结就是:dyV28资讯网——每日最新资讯28at.com

  • toString:返回有效数字,必要的时候使用科学计数法。
  • toPlainString: 不实用任何科学计数法。
  • toEngineeringString:必要的时候使用工程计数法。

五、总结

本文从精度的比较、除法运算中是否设置精度、对象初始化到转字符串,四个角度来把 BigDecimal 的坑尽可能清晰的描述出来,以及基于这些坑得到的优秀实践。dyV28资讯网——每日最新资讯28at.com

有些场景下推荐使用 BigDecimal ,但是能不用还是不用,比 double 、float 多出来的性能损失得是你能接受的。如果非得用,那上面这几个坑一定要规避。dyV28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-91825-0.html不掌握 BigDecimal 的四大坑你敢用吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: C语言字符串为什么以/0 作为结束标志?

下一篇: 从0到1,手把手教你部署自己的线上项目

标签:
  • 热门焦点
  • K60至尊版狂暴引擎2.0加持:超177万跑分斩获性能第一

    Redmi的后性能时代战略发布会今天下午如期举办,在本次发布会上,Redmi公布了多项关于和联发科的深度合作,以及新机K60 Ultra在软件和硬件方面的特性,例如:“K60 至尊版,双芯旗舰
  • 5月iOS设备好评榜:iPhone 14仅排第43?

    来到新的一月,安兔兔的各个榜单又重新汇总了数据,像安卓阵营的榜单都有着比较大的变动,不过iOS由于设备的更新换代并没有那么快,所以相对来说变化并不大,特别是iOS好评榜,老款设
  • 自动化在DevOps中的力量:简化软件开发和交付

    自动化在DevOps中扮演着重要角色,它提升了DevOps的效能。通过自动化工具和方法,DevOps团队可以实现以下目标:消除手动和重复性任务。简化流程。在整个软件开发生命周期中实现更
  • 从零到英雄:高并发与性能优化的神奇之旅

    作者 | 波哥审校 | 重楼作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得
  • 一个注解实现接口幂等,这样才优雅!

    场景码猿慢病云管理系统中其实高并发的场景不是很多,没有必要每个接口都去考虑并发高的场景,比如添加住院患者的这个接口,具体的业务代码就不贴了,业务伪代码如下:图片上述代码有
  • 拼多多APP上线本地生活入口,群雄逐鹿万亿市场

    Tech星球(微信ID:tech618)文 | 陈桥辉 Tech星球独家获悉,拼多多在其APP内上线了“本地生活”入口,位置较深,位于首页的“充值中心”内,目前主要售卖美食相关的
  • 腾讯盖楼,字节拆墙

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之“想重温暴刷深渊、30+技能搭配暴搓到爽的游戏体验吗?一起上晶核,即刻暴打!”曾凭借直播腾讯旗下代理格斗游戏《DNF》一
  • 三星Galaxy Z Fold/Flip 5国行售价曝光 :最低7499元/12999元起

    据官方此前宣布,三星将于7月26日也就是明天在韩国首尔举办Unpacked活动,届时将带来带来包括Galaxy Buds 3、Galaxy Watch 6、Galaxy Tab S9、Galaxy
  • 世界人工智能大会国际日开幕式活动在世博展览馆开启

    30日上午,世界人工智能大会国际日开幕式活动在世博展览馆开启,聚集国际城市代表、重量级院士专家、国际创新企业代表,共同打造人工智能交流平台。上海市副市
Top