前言

最近在维护一个C#项目,甲方提出了一个bug,如下:

double精度丢失 swift double精度不够_取整


也就是说,用户输入了一个有两位小数的数字,但是我们的校验发生了错误。

问题

项目中原始代码的校验方法如下:

double精度丢失 swift double精度不够_java_02


代码中将用户输入小数的100倍取整,并和这个小数的100倍进行比较,若小于则说明用户输入了两位以上的小数。

思路其实很简单,如果用户输入的小于两位小数,那么他的100倍取整和他的100倍应该是相等的,反之,应该是小于的关系。

比如用户输入12.345,则money为12.345,按照预期,centFloor为1234,而cent为1234.5,centFloor<cent,用户也确实输入了多于小数。

想法很美好,可是现实很残忍,问题就出在了代码中使用double进行计算,double作为双精度浮点数,本来就是由若干位的底数和指数的形式表示的,它本来就是不精确的,用它计算得到的结果也更是不精确的。所以只能用它进行存储,不能用它进行计算

以甲方提出的输入525.70为例,这里计算结果如下:

double精度丢失 swift double精度不够_java_03


我们可以看到cent并不是我们所期待的52570,而是一个精确的小数,这样,我们的判断就出现了问题。

实际上只要用户输入的数为小数,那么他的100倍和他的100倍再取整就不可能相等!

之前学Java的时候,有注意到过这个问题。

Java中解决double计算问题的方法是包装器类BigDecimal,代码如下:

BigDecimal a=new BigDecimal(0.1);
BigDecimal b=new BigDecimal(0.2);
System.out.println(a.add(b));
BigDecimal c=new BigDecimal("0.1");
BigDecimal d=new BigDecimal("0.2");
System.out.println(c.add(d));

执行结果如下:

double精度丢失 swift double精度不够_取整_04


需要注意的是,BigDecimal初始化的参数同样不能用double类型的变量(Java中小数默认是double),要使用String类型进行初始化。

在C#中,我发现使用decimal类型可以支持带小数的财务计算。

将上述的double换成decimal后,解决了问题:

double精度丢失 swift double精度不够_取整_05


double精度丢失 swift double精度不够_double精度丢失 swift_06


但是还是不推荐这种写法,正则表达式不香嘛?