前言
最近在维护一个C#项目,甲方提出了一个bug,如下:
也就是说,用户输入了一个有两位小数的数字,但是我们的校验发生了错误。
问题
项目中原始代码的校验方法如下:
代码中将用户输入小数的100倍取整,并和这个小数的100倍进行比较,若小于则说明用户输入了两位以上的小数。
思路其实很简单,如果用户输入的小于两位小数,那么他的100倍取整和他的100倍应该是相等的,反之,应该是小于的关系。
比如用户输入12.345,则money为12.345,按照预期,centFloor为1234,而cent为1234.5,centFloor<cent,用户也确实输入了多于小数。
想法很美好,可是现实很残忍,问题就出在了代码中使用double进行计算,double作为双精度浮点数,本来就是由若干位的底数和指数的形式表示的,它本来就是不精确的,用它计算得到的结果也更是不精确的。所以只能用它进行存储,不能用它进行计算
以甲方提出的输入525.70为例,这里计算结果如下:
我们可以看到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));
执行结果如下:
需要注意的是,BigDecimal初始化的参数同样不能用double类型的变量(Java中小数默认是double),要使用String类型进行初始化。
在C#中,我发现使用decimal类型可以支持带小数的财务计算。
将上述的double换成decimal后,解决了问题:
但是还是不推荐这种写法,正则表达式不香嘛?