处理BigDecimal在金额计算中丢失精度
首先我们先用一段代码复现问题根源,如下所示:
public static void main(String[] args) { BigDecimal bigDecimal=new BigDecimal(88); System.out.println(bigDecimal); bigDecimal=new BigDecimal("8.8"); System.out.println(bigDecimal); bigDecimal=new BigDecimal(8.8); System.out.println(bigDecimal);}
通过测试发现,当使用double或者float这些浮点数据类型时,会丢失精度,String、int则不会;这是为什么呢?
我们点开构造器方法看下源码:
public static long doubleToLongBits(double value) { long result = doubleToRawLongBits(value); // Check for NaN based on values of bit fields, maximum // exponent and nonzero significand. if ( ((result & DoubleConsts.EXP_BIT_MASK) == DoubleConsts.EXP_BIT_MASK) && (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L) result = 0x7ff8000000000000L; return result;}
问题就处在 doubleToRawLongBits 这个方法上,在jdk中double类(float与int对应)中提供了double与long转换,doubleToRawLongBits就是将double转换为long,这个方法是原始方法(底层不是java实现,是c++实现的)。double之所以会出问题,是因为小数点转二进制丢失精度。BigDecimal在处理的时候把十进制小数扩大N倍让它在整数上进行计算,并保留相应的精度信息
-
float和double类型,主要是为了科学计算和工程计算而设计的,之所以执行二进制浮点运算,是为了在广泛的数值范围上提供较为精确的快速近和计算。
-
并没有提供完全精确的结果,所以不应该被用于精确的结果的场合。
-
当浮点数达到一定大的数,就会自动使用科学计数法,这样的表示只是近似真实数而不等于真实数。
-
当十进制小数位转换二进制的时候也会出现无限循环或者超过浮点数尾数的长度。
总结
所以,在涉及到精度计算的过程中,我们尽量使用String类型来进行转换,正确用法如下:
BigDecimal bigDecimal2=new BigDecimal("8.8");BigDecimal bigDecimal3=new BigDecimal("8.812");System.out.println( bigDecimal2.compareTo(bigDecimal3));System.out.println( bigDecimal2.add(bigDecimal3));
工具分享
所以在这里整理出一个util类供大家使用。
import java.math.BigDecimal;/** * @Author shuaige * @Date 2022/4/17 * @Version 1.0 **/public class BigDecimalUtils { public static BigDecimal doubleAdd(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.add(b2); } public static BigDecimal floatAdd(float v1, float v2) { BigDecimal b1 = new BigDecimal(Float.toString(v1)); BigDecimal b2 = new BigDecimal(Float.toString(v2)); return b1.add(b2); } public static BigDecimal doubleSub(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.subtract(b2); } public static BigDecimal floatSub(float v1, float v2) { BigDecimal b1 = new BigDecimal(Float.toString(v1)); BigDecimal b2 = new BigDecimal(Float.toString(v2)); return b1.subtract(b2); } public static BigDecimal doubleMul(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.multiply(b2); } public static BigDecimal floatMul(float v1, float v2) { BigDecimal b1 = new BigDecimal(Float.toString(v1)); BigDecimal b2 = new BigDecimal(Float.toString(v2)); return b1.multiply(b2); } public static BigDecimal doubleDiv(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); // 保留小数点后两位 ROUND_HALF_UP = 四舍五入 return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP); } public static BigDecimal floatDiv(float v1, float v2) { BigDecimal b1 = new BigDecimal(Float.toString(v1)); BigDecimal b2 = new BigDecimal(Float.toString(v2)); // 保留小数点后两位 ROUND_HALF_UP = 四舍五入 return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP); } /** * 比较v1 v2大小 * @param v1 * @param v2 * @return v1>v2 return 1 v1=v2 return 0 v1<v2 return -1 */ public static int doubleCompareTo(double v1, double v2) { BigDecimal b1 = new BigDecimal(Double.toString(v1)); BigDecimal b2 = new BigDecimal(Double.toString(v2)); return b1.compareTo(b2); } public static int floatCompareTo(float v1, float v2) { BigDecimal b1 = new BigDecimal(Float.toString(v1)); BigDecimal b2 = new BigDecimal(Float.toString(v2)); return b1.compareTo(b2); }}