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

科技   2024-12-30 14:35   安徽  

来源:juejin.cn/post/7348709938023940136

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 77w+ 字,讲解图 3088+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2600+小伙伴加入

  • 类介绍
  • 从例子入手
  • 结论

在金融领域,为了保证数据的精度,往往会使用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方法:

@Test
public 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代码块(这块是关键):

  • 首先17行校验了一下数值范围
  • 18行将ys扩大了10的n次倍,这里n=raise=1,所以返回的scaledY=350
  • 接着就进入到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,实现最终结果的计算。

另外BigDecimal在使用时有一些注意事项,可以参考以下文档学习:

https://javaguide.cn/java/basis/bigdecimal.html

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 77w+ 字,讲解图 3088+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2600+小伙伴加入


1. 我的私密学习小圈子,从0到1手撸企业实战项目!

2. MJGA,让Java再次伟大,开箱即用Springboot怕不怕Gin?

3. 能用到 “退休” 的 600条 Linux 命令,直接起飞!

4. 一款高颜值的在线项目任务管理神器

最近面试BAT,整理一份面试资料Java面试BATJ通关手册,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。

获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。

PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下在看,加个星标,这样每次新文章推送才会第一时间出现在你的订阅列表里。

“在看”支持小哈呀,谢谢

小哈学Java
码龄9年,前某厂中台研发。专注于Java领域干货分享,不限于BAT面试, 算法,数据库,Spring Boot, 微服务,高并发, JVM, Docker容器,ELK相关知识,期待与您一同进步。
 最新文章