最近做了一个有关接入微信支付的需求,其中微信支付接口的有关金额的字段值单位都为分,如下红框所示。

一般我们业务保存和展示的金额单位都是元,值包含小数点。所以在调用微信支付接口时我们要把单位为元的金额(Double类型)转成单位为分的金额(Integer类型),涉及到浮点运算,在开发中踩了一个坑,与大家分享。
刚开始使用Double计算货币金额,如下代码所示:
public class DoubleTest {
public static void main(String[] args) {
double yuanDouble = 10.2d;
System.out.println(yuanDouble);
double fenDouble = yuanDouble * 100;
System.out.println(fenDouble);
int fenInteger = (int)fenDouble; // 截断成int
System.out.println(fenInteger);
}
}
执行结果如下,单位为元的10.2转成单位为分的1019,发生精度资损。
10.2
1019.9999999999999
1019
因此,float和double类型主要是为了科学计算和工程计算而设计的。他们执行二进制浮点运算,这是为了在广泛的数字范围上提供较为精确的快速近似计算而精心设计的。然而,它们并没有提供完全精确的结果,所以我们不应该用于精确计算的场合。float和double类型尤其不适合用于货币运算,因为要让一个float或double精确的表示0.1或者10的任何其他负数次方值是不可能的(其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10)。
浮点运算很少是精确的,只要是超过精度能表示的范围就会产生误差。往往产生误差不是因为数的大小,而是因为数的精度。因此,产生的结果接近但不等于想要的结果。尤其在使用 float 和 double 作精确运算的时候要特别小心,比如货币金额计算。
解决这个问题的正确办法是使用BigDecimal进行货币金额计算,示例如下:
import java.math.BigDecimal;
public class DoubleTest {
private static double mul(double d1,double d2){
BigDecimal bd1 = new BigDecimal(Double.toString(d1));
BigDecimal bd2 = new BigDecimal(Double.toString(d2));
return bd1.multiply(bd2).doubleValue();
}
public static void main(String[] args) {
double yuanDouble = 10.2d;
System.out.println(yuanDouble);
double fenDouble = mul(yuanDouble, 100);
System.out.println(fenDouble);
int fenInteger = (int)fenDouble; // 截断成int
System.out.println(fenInteger);
}
}
运行结果如下,这才是正确的货币金额计算方式。
10.2
1020.0
1020
但是,使用BigDecimal有两个缺点:与使用基本运算类型相比,这样做很不方便,而且很慢。对于解决货币计算的精度问题,后一种缺点并不要紧,但是前一种缺点可能会让你使用很不舒服。
总之,对于任何需要精度答案的计算任务,比如货币计算,请不要使用float或者double。如果你想让系统记录十进制小数点,并且不介意因为不使用基本类型而带来的不便,就请使用BigDecimal。
网友评论