BigDecimal的应用

https://blog.csdn.net/yinghuananhai/article/details/81260327

https://zhidao.baidu.com/question/1990509580432335027.html

https://blog.csdn.net/shenziheng1/article/details/79471340

https://blog.csdn.net/en_joker/article/details/86589691
 

先看 为什么double和float 计算的结果 会有一点点的误差:

这个涉及到二进制与十进制的转换问题。
N进制可以理解为:数值×基数的幂,例如我们熟悉的十进制数123.4=1×10²+2×10+3×(10的0次幂)+4×(10的-1次幂);其它进制的也是同理,例如二进制数11.01=1×2+1×(2的0次幂)+0+1×(2的-2次幂)=十进制的3.25。
double类型的数值占用64bit,即64个二进制数,除去最高位表示正负符号的位,在最低位上一定会与实际数据存在误差(除非实际数据恰好是2的n次方)。

举个例子来说,比如要用4bit来表示小数3.26,从高到低位依次对应2的1,0,-1,-2次幂,根据最上面的分析,应当在二进制数11.01(对应十进制的3.25)和11.10(对应十进制的3.5)之间选择。
简单来说就是我们给出的数值,在大多数情况下需要比64bit更多的位数才能准确表示出来(甚至是需要无穷多位),而double类型的数值只有64bit,后面舍去的位数一定会带来误差,无法得到“数学上精确”的结果。

 

所以  BigDecimal能精确计算的原因 在于 它的底层计算不是使用的二进制。

 

我们根据一个BigDecimal的add方法 debug 看一下源码:

 BigDecimal a1=new BigDecimal("0.111");

        BigDecimal b1=new BigDecimal("0.2");

        System.out.println(a1.add(b1));

BigDecimal类中 有以下几个静态变量:

// Cache of common small BigDecimal values.
private static final BigDecimal zeroThroughTen[] = {
// Cache of zero scaled by 0 - 15
private static final BigDecimal[] ZERO_SCALED_BY = {
private static final long[] LONG_TEN_POWERS_TABLE = {
private static volatile BigInteger BIG_TEN_POWERS_TABLE[] = {
private static final long THRESHOLDS_TABLE[] = {

主要作用是 限制了BigDecimal的计算范围,以及精度范围 scale ,还有作为缓存 提高计算效率。

BigDecimal 的最大最小值 以Long 为基础,

 

不超出边界的计算 都会 落入到 

return add(this.intCompact, this.scale, augend.intCompact, augend.scale);

 

进入BigDecimal 的 

private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) 

先比较两个 小数位数精度的大小,以大的 作为和的精度。

进入if分支: 

  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);
            }

 

0.111 的scale是3位, 0.2的精度是 1位, 所以 以3为准, 然后0.2就要乘以10的2次方(  2是(3-1)的差值) 。

然后 使用111 + 200 得到 311 的值。

最后 311 除以  小数位数精度的大的值0.111的位数 作为小数部分的实际值。

 

其中 在

long scaledY = longMultiplyPowerTen(ys, raise);方法中,用到了 
LONG_TEN_POWERS_TABLE 和 THRESHOLDS_TABLE 两个变量,
LONG_TEN_POWERS_TABLE 表示 两个数字的精度差值 最多只有18位,否则直接就是 Long.MIN_VALUE的值(因为超出范围了)。
并且这个精度差值 是需要乘以的10的幂次方: 2* 10的2次方()
THRESHOLDS_TABLE 表示具体的数字范围, 如果精度差值为 0 ,那么计算的值 需要小于 Long.MAX_VALUE的值,即THRESHOLDS_TABLE 数组的第一个变量, 如果如果精度差值为 1,,那么计算的值 需要小于 Long.MAX_VALUE/10L的值,即THRESHOLDS_TABLE 数组的第二个变量; 依次类推 。。 保证 return val * tenpower;的计算结果 不溢出。

 

再往下走,是小数部分的 long值 相加, 然后调用 BigDecimal.valueOf(sum, scale);

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);
    }

 此时得到一个long类型的 unscaledVal 值为 311 ,scale 为 3 ,

 

最终调用构造函数:

BigDecimal(BigInteger intVal, long val, int scale, int prec) {
    this.scale = scale;
    this.precision = prec;
    this.intCompact = val;
    this.intVal = intVal;
}

 

得到最终的结果值 : 0.311

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值