前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >下次谁再这么用BigDecimal就直接开除

下次谁再这么用BigDecimal就直接开除

原创
作者头像
半月无霜
发布2024-11-21 18:03:20
发布2024-11-21 18:03:20
16500
代码可运行
举报
文章被收录于专栏:半月无霜半月无霜
运行总次数:0
代码可运行

今日推荐文章:使用云开发平台 0 代码开发一个 AI 智能助手小程序-腾讯云开发者社区-腾讯云

点评:该文章指导用户如何利用腾讯云开发平台创建一个AI智能助手小程序,文章从基础准备工作开始,详细介绍了小程序账号的注册、云开发环境的搭建、AI Agent的创建与配置,直至小程序应用的设计、测试与发布,提供了一条清晰的开发路径;是非常值得学习操作的一篇文章

一、介绍

BigDecimal大家都不陌生;BigDecimalJava编程语言中的一个类,它位于 java.math 包中。这个类提供了任意精度的定点数,非常适合处理货币计算或其他需要精确小数运算的场景。

但是,这个BigDecimal使用起来有坑,请注意。

标题是我夸张写的,但千万不要踩踏这些坑。

二、BigDecimal的坑

1)初始化的坑

首先看一下下面这段代码,它会通过这段单测吗

代码语言:javascript
代码运行次数:0
运行
复制
 package com.banmoon;
 ​
 import org.junit.Assert;
 import org.junit.Test;
 ​
 import java.math.BigDecimal;
 ​
 public class BigDecimalTest {
 ​
     @Test
     public void initializeTest() {
         BigDecimal v1 = new BigDecimal("0.01");
         BigDecimal v2 = new BigDecimal(0.01);
         Assert.assertEquals(0, v1.compareTo(v2));
     }
 ​
 }

如果你的回答是通过,那么你就踩坑了

image-20241120151327068
image-20241120151327068

为什么会这样呢,我决定打印一下他们的值,结果如下

image-20241120151505532
image-20241120151505532

可是为什么会这样呢?

原来,当我们使用这个Double类型传入时,BigDecimal为了不丢失Double的精度,将精度完完整整的保留了下来。

如果有需要将Double类型转为BigDecimal类型的时候,建议使用BigDecimal.valueOf(0.01);

重新执行一下单测,查看效果

代码语言:javascript
代码运行次数:0
运行
复制
 package com.banmoon;
 ​
 import org.junit.Assert;
 import org.junit.Test;
 ​
 import java.math.BigDecimal;
 ​
 public class BigDecimalTest {
 ​
     @Test
     public void initializeTest() {
         BigDecimal v1 = new BigDecimal("0.01");
         BigDecimal v2 = BigDecimal.valueOf(0.01);
         System.out.println("v1:" + v1);
         System.out.println("v2:" + v2);
         Assert.assertEquals(0, v1.compareTo(v2));
     }
 ​
 }
image-20241120152027209
image-20241120152027209

2)equals的坑

同样,先看看下面这段代码,并给出是否通过

代码语言:javascript
代码运行次数:0
运行
复制
 package com.banmoon;
 ​
 import org.junit.Assert;
 import org.junit.Test;
 ​
 import java.math.BigDecimal;
 ​
 public class BigDecimalTest {
 ​
     @Test
     public void equalsTest() {
         BigDecimal v1 = new BigDecimal("0.01");
         BigDecimal v2 = new BigDecimal("0.010");
         System.out.println("v1:" + v1);
         System.out.println("v2:" + v2);
         Assert.assertEquals(v1, v2);
     }
 ​
 }

很多人一眼看上去,就认定为这两个数字是相等的。但结果却是

image-20241120154323832
image-20241120154323832

数值是相等的没错,但找到不同了吗?没错,它俩的精度不同,我们来看下源码

image-20241120153410101
image-20241120153410101

可以看到,在equals()方法中,除了对比数值大小,还对比了精度;

所以如果仅仅需要对比数值大小的话,还是使用compareTo()方法吧

这个方法来自于Comparable<T>接口,后续我们可以讲讲这个接口的使用 简单使用如下,使用int result = v1.compareTo(v2)v1 > v2时,result = 1; 当v1 == v2时,result = 0; 当v1 < v2时,result = -1

3)divide的坑

同样这段代码,非常容易理解,会不会以为精度的问题,导致测试不通过?

代码语言:javascript
代码运行次数:0
运行
复制
 package com.banmoon;
 ​
 import org.junit.Assert;
 import org.junit.Test;
 ​
 import java.math.BigDecimal;
 ​
 public class BigDecimalTest {
 ​
     @Test
     public void divideTest() {
         BigDecimal v1 = new BigDecimal("1");
         BigDecimal v2 = new BigDecimal("3");
         BigDecimal result = v1.divide(v2);
         System.out.println(result);
         Assert.assertEquals(0, result.compareTo(new BigDecimal("0.33")));
     }
 ​
 }

但实际上,在执行divide()的时候,它就报错了,根本走不到断言那一步

image-20241120155624649
image-20241120155624649

这实际上也是跟精度有关,大家都知道1/3是一个无线循环小数,如果我们没有指定结果精度,它就会一直无限循环下去,然后会导致异常

这在源码的说明上也有体现

image-20241120174546668
image-20241120174546668

建议指定精度,并且加上舍入模式,java.math.RoundingMode

4)乘除执行顺序

代码语言:javascript
代码运行次数:0
运行
复制
 package com.banmoon;
 ​
 import org.junit.Assert;
 import org.junit.Test;
 ​
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 ​
 public class BigDecimalTest {
 ​
     @Test
     public void divideTest() {
         BigDecimal v1 = new BigDecimal("1");
         BigDecimal v2 = new BigDecimal("3");
         // 1 / 3 * 3
         BigDecimal m1 = v1.divide(v2, 2, RoundingMode.HALF_UP).multiply(v2);
         // 1 * 3 / 3
         BigDecimal m2 = v1.multiply(v2).divide(v2, 2, RoundingMode.HALF_UP);
         System.out.println("m1:" + m1);
         System.out.println("m2:" + m2);
         Assert.assertEquals(0, m1.compareTo(m2));
     }
 ​
 }

这段代码大家肯定都猜到了,由于精度的问题,先除的要避免报错,会不可避免的丢失掉精度

image-20241121164403180
image-20241121164403180

所以为了避免这种事情的发生,要额外注意乘除的先后顺序

三、最后

出了BUG不会被开除,那是我为了吸引你进来特地写的

但请不要再犯这么些错误了

另外再提供一下BigDecimal的加减乘除等基本方法

BigDecimal 是 Java 中用于高精度浮点数运算的一个类。它特别适用于金融计算等需要精确控制小数位数和舍入规则的场景。

  1. 加法
    • 使用 add(BigDecimal) 方法。

    BigDecimal a = new BigDecimal("1.23"); BigDecimal b = new BigDecimal("4.56"); BigDecimal sum = a.add(b); // sum = 5.79

  2. 减法
    • 使用 subtract(BigDecimal) 方法。

    BigDecimal difference = a.subtract(b); // difference = -3.33

  3. 乘法
    • 使用 multiply(BigDecimal) 方法。

    BigDecimal product = a.multiply(b); // product = 5.6088

  4. 除法
    • 使用 divide(BigDecimal, int, RoundingMode) 方法,需要指定结果的小数位数和舍入模式。

    BigDecimal quotient = a.divide(b, 2, RoundingMode.HALF_UP); // quotient = 0.27

舍入模式(RoundingMode):

  • ROUND_UP:远离零方向舍入。
  • ROUND_DOWN:向零方向舍入。
  • ROUND_CEILING:向正无穷方向舍入。
  • ROUND_FLOOR:向负无穷方向舍入。
  • ROUND_HALF_UP:四舍五入,相当于通常的四舍五入。
  • ROUND_HALF_DOWN:五舍六入。
  • ROUND_HALF_EVEN:银行家舍入法,四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一。

在使用 BigDecimal 进行运算时,由于其不可变性,每次运算都会返回一个新的 BigDecimal 对象,原对象不会被改变。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、介绍
  • 二、BigDecimal的坑
    • 1)初始化的坑
    • 2)equals的坑
    • 3)divide的坑
    • 4)乘除执行顺序
  • 三、最后
    • 舍入模式(RoundingMode):
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档