BigDecimal 详解 清晰版

前言

当我们进行浮点数运算的时候会产生精度丢失的问题

float a = 2.0f - 1.9f;
float b = 1.8f - 1.7f;
System.out.println(a);// 0.100000024
System.out.println(b);// 0.099999905
System.out.println(a == b);// false

首先来看一下,为什么会有精度丢失 ?

是因为在计算机中,数据是以二进制的形式表示,但不是所有的十进制数字都可以由二进制数字进行表示,例如0.1这个数字在二进制中是一个无限循环的小数,所以像这样的数就会精度丢失。

我们可以使用BigDecimal来避免精度丢失。

BigDecimal原理

public class BigDecimal {
    //值的绝对long型表示
    private final transient long intCompact;
    //值的小数点后的位数
    private final int scale;
 
    private final BigInteger intVal;
    //值的有效位数,不包含正负符号
    private transient int precision;
    private transient String stringCache;
     
    //加、减、乘、除、绝对值
    public BigDecimal add(BigDecimal augend) {}
    public BigDecimal subtract(BigDecimal subtrahend) {}
    public BigDecimal multiply(BigDecimal multiplicand) {}
    public BigDecimal divide(BigDecimal divisor) {}
    public BigDecimal abs() {}
}

上面是BigDecimal类,里面有两个重要的参数

  • intCompact
  • scale

因为十进制转换二进制会出现问题,所以我们将数字扩大N倍,让其在整数的维度上进行计算,二intCompact用来保存这个数字。

而scale用来表示小数点后的位数,这样在intCompact和scale的作用下,就可以保证计算不会受到二进制的限制。

举个例子,如果我们计算0.1和0.2,BigDecimal会将他们转换为整数1和2,然后设置scale为1,之后在进行计算。

上面两个参数分别是BigDecimal的两个原理,分别为精确数值表示和扩大为整数运算。

还有两个原理分别是

BigDecimal提供了一系列方法来进行数学运算,例如add,subtract,multiply,divide。

有避免精度丢失的构造方法。

BigDecimal b = new BigDecimal("0.1")//里面是String类型
//不可以是double。这样在入参的时候就会产生精度丢失

BigDecimal等值比较问题

BigDecimal等值比较应使用compareTo()方法,而不是equals()方法。

public boolean equals(Object x) {
    if (!(x instanceof BigDecimal)) {
        return false;
    }
    BigDecimal xDec = (BigDecimal)x;
    if (x == this) {
        return true;
    } else if (this.scale != xDec.scale) {
        // 如果标度值不同,则返回false。
        // 所以,1.0和 1.00不相等(前者标度值为1,后者标度值为2)
        return false;
    } else {
        // 进行值比较
        long s = this.intCompact;
        long xs = xDec.intCompact;
        if (s != -9223372036854775808L) {
            if (xs == -9223372036854775808L) {
                xs = compactValFor(xDec.intVal);
            }
            return xs == s;
        } else if (xs != -9223372036854775808L) {
            return xs == compactValFor(this.intVal);
        } else {
            return this.inflated().equals(xDec.inflated());
        }
    }
}

public int compareTo(BigDecimal val) {
    if (this.scale == val.scale) {
        long xs = this.intCompact;
        long ys = val.intCompact;
        if (xs != -9223372036854775808L && ys != -9223372036854775808L) {
            return xs != ys ? (xs > ys ? 1 : -1) : 0;
        }
    }
    // 如果标度值不同,会将标尺值对齐后再比较
    // 所以,不存在1.0和 1.00不相等的情况。
    int xsign = this.signum();
    int ysign = val.signum();
    if (xsign != ysign) {
        return xsign > ysign ? 1 : -1;
    } else if (xsign == 0) {
        return 0;
    } else {
        // compareMagnitude会对齐标度值,这里不展开,有兴趣的可以阅读相关源码
        int cmp = this.compareMagnitude(val);
        return xsign > 0 ? cmp : -cmp;
    }
}

        分析源码可知,在使用equals()方法比较两个BigDecimal时,如果标度值(scale)不同,则返回false。这对于标度值不同,值相同的场景不适用。

        而compareTo()则没有这个问题。所以使用BigDecimal进行等值比较时,应使用compareTo()方法,而不是equals()方法。

 总结

        本文较为清晰的说明了BigDecimal的原理和等值比较问题,其中原理部分解释为什么BigDecimal可以解决精度丢失问题,等值比较部分,也放出源码,从源头上解决了equals和compareTo()的不同的原因。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值