BigDecimal

前言#

最近接触到的公司的项目或多或少都在和钱打交道,而和钱相关的变量并没用float或是double去表示,因为丢失精度的问题相信都有所耳闻。举个最简单的例子吧,如果运行一下System.out.println(1.0-0.8);,会发现结果并不是我们预期的0.2,而是0.19999999999999996

这是因为计算机底层都是采用二进制的方式去存储数值的,显然0.8无法用任何一个二进制数精确表示,最终导致结果出现了一定的误差,这个可以参考你无法用一个十进制的数去表示1/3一样

所以很多涉及金钱的数值都采用BigDecimal类型去表示

有趣的是,我在上大学的时候也想过精度丢失的问题,当时想到一个很朴素的方法,就是把数字转化为整形数组,然后逐位去进行运算,这个看起来是比较简单的一个思路,但是在实现起来要考虑到进借位等一大串问题

好在 Java 在 java.math 包中提供了解决这类问题的 api ,但是我一直好奇 BigDecimal 到底是怎样实现的。后来通过打断点追源码和查资料的方式觉得大概能用一句话去简单概括它的原理:把十进制小数扩大N倍让它在整数的维度上进行计算,并保留相应的精度信息

例如 0.8 不能用有限的二进制数表示,那就将它扩大到原来的 10 倍, 8 终归是可以用十进制数表示的

构造方法#

BigDecimal 一共有四种构造方法

  • BigDecimal(int value)
  • BigDecimal(double value)
  • BigDecimal(long value)
  • BigDecimal(String value)

其中,第二种构造方法:从 double 中构造是不被推荐使用的。原因如下

//输出结果将是0.8000000000000000444089209850062616169452667236328125
BigDecimal bgDouble = new BigDecimal(0.8);
System.out.println(bgDouble);      

很神奇,用 BigDecimal 明明是为了获得更好的精度,但为什么还是出现了精度丢失的问题?根据查阅资料,大概得到以下的结论:

  • 参数类型为 double 的构造方法具有一定的不可预知性。你以为传进去的参数是 0.8 ,但是不要忘了,这个数字无法用有限的二进制数表示,所以还是会出现精度丢失的问题。侧面的说明,出现这个问题不是 BigDecimal 本身的问题,而是 double 类型的问题
  • 参数为 String 的构造方法是完全可预知的,因为它是所见即所得。所以更推荐使用BigDecimal(String value)
  • 如果确实需要将 double 转化为 BigDecimal,用Double.toString(double)转成 String ,然后使用 String 构造方法,或使用 BigDecimal 的静态方法valueOf
BigDecimal bgDouble1 = new BigDecimal(Double.toString(0.8));
BigDecimal bgDouble2 = BigDecimal.valueOf(0.8);
System.out.println(bgDouble1);
System.out.println(bgDouble2);

常用 api#

scale()#

BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2,两位小数
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0

setScale()#

public class Main {
    public static void main(String[] args) {
        BigDecimal d1 = new BigDecimal("123.456789");
        BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
        BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
    }
}

BigDecimal 在做加、减、乘的时候不会出现精度丢失的情况,但是在除的时候可能会出现除不尽导致精度丢失的情况,这时候就需要指定要保留的位数以及如何进行截断

加减乘除#

public BigDecimal add(BigDecimal value);                        //加法

public BigDecimal subtract(BigDecimal value);                   //减法 

public BigDecimal multiply(BigDecimal value);                   //乘法

public BigDecimal divide(BigDecimal value);                     //除法

还是除法的老问题,可能涉及到除不尽,但其实divide()方法可以传递三个参数

//参数1:除数
//参数2:保留位数
//参数3:舍入模式
public BigDecimal divide(BigDecimal divisor, int scale, int roundingMode)
ROUND_CEILING        //向正无穷方向舍入

ROUND_DOWN           //向零方向舍入

ROUND_FLOOR          //向负无穷方向舍入

ROUND_HALF_DOWN      //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向下舍入, 例如1.55 保留一位小数结果为1.5

ROUND_HALF_EVEN      //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,如果保留位数是奇数,使用ROUND_HALF_UP,如果是偶数,使用ROUND_HALF_DOWN

ROUND_HALF_UP        //向(距离)最近的一边舍入,除非两边(的距离)是相等,如果是这样,向上舍入, 1.55保留一位小数结果为1.6

ROUND_UNNECESSARY    //计算结果是精确的,不需要舍入模式

ROUND_UP             //向远离0的方向舍入

除此之外, BigDecimal 还提供了 divideAndRemainder()方法,该方法的返回值是 BigDecimal 类型数组,分别是商和余数

比较#

如果使用equals()去比较两个 BigDecimal 数,将会比较数值和精度,只有二者都相同时才会返回 true 。当只想比较数值的时候,可以使用compareTo()方法,该方法的返回值有负数、正数和0,分别对应着小于、大于、等于

最后说一句#

BigDecimal 是一个不可变对象,进行四则运算时产生的返回值都是一个新的对象



参考内容
BigDecimal-百度百科

posted @   colee51666  阅读(150)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示
主题色彩
哥伦布
14:09发布
哥伦布
14:09发布
0°
西北风
6级
空气质量
相对湿度
74%
今天
小雪
-1°/6°
周五
雨夹雪
0°/7°
周六
多云
-1°/7°