金额计算使用BigDecimal精确度问题

  • Post author:
  • Post category:其他




BigDecimal精确度问题

很多涉及金额计算不能使用double、float等类型,而是使用精确度更高的bigdecimal;

所以,很多支付、电商、金融等业务中,BigDecimal的使用非常频繁。但是,如果误以为只要使用BigDecimal表示数字,结果就一定精确,那就大错特错了!

阿里巴巴手册中有一条禁止使用构造方法bigdecimal(double)方式把double值转化为bigdecimal对象

在这里插入图片描述

那到底应该如何正确的创建一个BigDecimal?

上图推荐了两种方式new bigdecimal(“0.1”)、bigdecimal.valueOf(0.1)


那么BigDecimal是如何做精确度计算的呢


如果大家看过BigDecimal的源码,其实可以发现,实际上一个BigDecimal是通过一个”无标度值”和一个”标度”来表示一个数的。

在BigDecimal中,标度是通过scale字段来表示的。

而无标度值的表示比较复杂。当unscaled value超过阈值(默认为Long.MAX_VALUE)时采用intVal字段存储unscaled value,intCompact字段存储Long.MIN_VALUE,否则对unscaled value进行压缩存储到long型的intCompact字段用于后续计算,intVal为空;

在这里插入图片描述

scale()返回scale标度,其中注释非常清楚;

如果scale为零或正值,则该值表示这个数字小数点右侧的位数。如果scale为负数,则该数字的真实值需要乘以10的该负数的绝对值的幂。例如,scale为-3,则这个数需要乘1000,即在末尾有3个0。

如123.123,那么如果使用BigDecimal表示,那么他的无标度值为123123,他的标度为3。

而二进制无法表示的0.1,使用BigDecimal就可以表示了,及通过无标度值1和标度1来表示。

创建bigdecimal对象有四种方法;

bigdecimal(int)

bigdecimal(double)

bigdecimal(long)

bigdecimal(string)

四种方式创建出的标度scale都是不一样的;

其中 BigDecimal(int)和BigDecimal(long) 比较简单,因为都是整数,所以他们的标度都是0;

而BigDecimal(double) 和BigDecimal(String)的标度就有很多学问了;

BigDecimal中提供了一个通过double创建BigDecimal的方法——BigDecimal(double) ,但是,同时也给我们留了一个坑!

因为我们知道,double表示的小数是不精确的,如0.1这个数字,double只能表示他的近似值。

所以,当我们使用new BigDecimal(0.1)创建一个BigDecimal 的时候,其实创建出来的值并不是正好等于0.1的;

而是0.1000000000000000055511151231257827021181583404541015625。这是因为doule自身表示的只是一个近似值;

在这里插入图片描述

上图可以看出bigdecimal(double)的精度是不精确的,double本身就是一个近似值;

在这里插入图片描述

而bigdecimal(string)是精确的值,它的标度是1;

但是需要注意的是,new BigDecimal(“0.10000”)和new BigDecimal(“0.1”)这两个数的标度分别是5和1,如果使用BigDecimal的equals方法比较,得到的结果是false,具体原因和解决办法参考为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?

在这里插入图片描述

这里bigdecimal.valueOf(0.1)为什么也能精确呢,

使用了于double.tostring(),转化为第一种方式new bigdecimal(string)

在这里插入图片描述



版权声明:本文为qq_41973632原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。