BigDecimal为什么可以不丢失精度?

文摘   2024-11-23 10:01   山西  
在金融领域,为了保证数据的精度,往往会使用BigDecimal。本文就来探讨下为什么BigDecimal可以保证精度不丢失。

类介绍


首先来看一下BigDecimal的类声明以及几个属性:
public class BigDecimal extends Number implements Comparable<BigDecimal> {    // 该BigDecimal的未缩放值    private final BigInteger intVal;    // 精度,可以理解成小数点后的位数    private final int scale;    // BigDecimal中的十进制位数,如果位数未知,则为0(备用信息)    private transient int precision;    // Used to store the canonical string representation, if computed.    // 这个我理解就是存实际的BigDecimal值    private transient String stringCache;    // 扩大成long型数值后的值    private final transient long intCompact;}


从例子入手


通过debug来发现源码中的奥秘是了解类运行机制很好的方式。请看下面的testBigDecimal方法:
@Testpublic void testBigDecimal() {    BigDecimal bigDecimal1 = BigDecimal.valueOf(2.36);    BigDecimal bigDecimal2 = BigDecimal.valueOf(3.5);    BigDecimal resDecimal = bigDecimal1.add(bigDecimal2);    System.out.println(resDecimal);}

在执行了BigDecimal.valueOf(2.36)后,查看debug信息可以发现上述提到的几个属性被赋了值:


接下来进到add方法里面,看看它是怎么计算的:
/** * Returns a BigDecimal whose value is (this + augend),  * and whose scale is max(this.scale(), augend.scale()). */public BigDecimal add(BigDecimal augend) {    if (this.intCompact != INFLATED) {        if ((augend.intCompact != INFLATED)) {            return add(this.intCompact, this.scale, augend.intCompact, augend.scale);        } else {            return add(this.intCompact, this.scale, augend.intVal, augend.scale);        }    } else {        if ((augend.intCompact != INFLATED)) {            return add(augend.intCompact, augend.scale, this.intVal, this.scale);        } else {            return add(this.intVal, this.scale, augend.intVal, augend.scale);        }    }}

看一下传进来的值:


进入上面代码块第8行的add方法:
private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) {    long sdiff = (long) scale1 - scale2;    if (sdiff == 0) {        return add(xs, ys, scale1);    } else if (sdiff < 0) {        int raise = checkScale(xs,-sdiff);        long scaledX = longMultiplyPowerTen(xs, raise);        if (scaledX != INFLATED) {            return add(scaledX, ys, scale2);        } else {            BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys);            return ((xs^ys)>=0) ? // same sign test                new BigDecimal(bigsum, INFLATED, scale2, 0)                : valueOf(bigsum, scale2, 0);        }    } else {        int raise = checkScale(ys,sdiff);        long scaledY = longMultiplyPowerTen(ys, raise);        if (scaledY != INFLATED) {            return add(xs, scaledY, scale1);        } else {            BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs);            return ((xs^ys)>=0) ?                new BigDecimal(bigsum, INFLATED, scale1, 0)                : valueOf(bigsum, scale1, 0);        }    }}

这个例子中,该方法传入的参数分别是:xs=236,scale1=2,ys=35,scale2=1 该方法首先计算scale1 - scale2,根据差值走不同的计算逻辑,这里求出来是1,所以进入到最下面的else代码块(这块是关键):

  1. 首先17行校验了一下数值范围
  2. 18行将ys扩大了10的n次倍,这里n=raise=1,所以返回的scaledY=350
  3. 接着就进入到20行的add方法:
private static BigDecimal add(long xs, long ys, int scale){    long sum = add(xs, ys);    if (sum!=INFLATED)        return BigDecimal.valueOf(sum, scale);    return new BigDecimal(BigInteger.valueOf(xs).add(ys), scale);}

这个方法很简单,就是计算和,然后返回BigDecimal对象:


结论


所以可以得出结论:BigDecimal在计算时,实际会把数值扩大10的n次倍,变成一个long型整数进行计算,整数计算时自然可以实现精度不丢失。同时结合精度scale,实现最终结果的计算。
作者:Carybro
链接:https://juejin.cn/post/7348709938023940136

-END-


ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学找我领取。

以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言

程序媛山楂
5年+程序媛,专注于AI编程普及。本号主要分享AI编程、Chat账号、Chat教程、Sora教程、Suno AI、Sora账号、Sora提示词、AI换脸、AI绘画等,帮助解决各种AI疑难杂症。
 最新文章