[关闭]
@boothsun 2017-12-07T00:31:36.000000Z 字数 1914 阅读 1567

Fel表达式问题

Java


精度问题:

我们知道java中直接使用float和double参与的计算都可能会产生精度问题,比如0.1+0.3、1.0-0.9 等。所以一般财务系统,都会使用BigDecimal进行加减乘除。 在调研Fel过程中,发现Fel里进行计算都是使用浮点数加减乘除的,所以不可避免的会产生精度问题。

Case+源码分析:

加法 Case:

  1. FelEngine fel = new FelEngineImpl();
  2. Object result = fel.eval("0.1+0.2");
  3. System.out.println(result);



源码分析:

简单的来说,Fel首先经过词法解析器将表达式解析成FelNode实例,FelNode包含表达式子节点(比如+号、0.1等)和表达式解析器,解析器对应的有com.greenpineyu.fel.function.operator.Add、ccom.greenpineyu.fel.function.operator.Sub、com.greenpineyu.fel.function.operator.Mul、com.greenpineyu.fel.function.operator.Div等各种解析器(详见com.greenpineyu.fel.function.operator下的类),具体的表达式运行是由这些解析器计算的。具体到方法又是有com.greenpineyu.fel.function.operator.Add#call计算的。

  1. public Object call(FelNode node, FelContext context) {
  2. Object returnMe = null;
  3. for (Iterator<FelNode> iterator = node.getChildren().iterator(); iterator.hasNext();) {
  4. Object child = iterator.next();
  5. if (child instanceof FelNode) {
  6. FelNode childNode = (FelNode) child;
  7. child = childNode.eval(context);
  8. }
  9. if (child instanceof String) {
  10. if (returnMe == null) {
  11. returnMe = child;
  12. continue;
  13. }
  14. returnMe = returnMe + (String) child;
  15. }
  16. if (child instanceof Number) {
  17. if (returnMe == null) {
  18. returnMe = child;
  19. continue;
  20. }
  21. Number value = (Number) child;
  22. if (returnMe instanceof Number) {
  23. Number r = (Number) returnMe;
  24. // 注意这里:是直接使用转成double进行加减的。
  25. returnMe = toDouble(r) + toDouble(value);
  26. }else if(returnMe instanceof String){
  27. String r = (String) returnMe;
  28. returnMe=r+value;
  29. }
  30. }
  31. }
  32. if(returnMe instanceof Number){
  33. return NumberUtil.parseNumber(returnMe.toString());
  34. }
  35. return returnMe;
  36. }
  37. /**
  38. * 将Number转换成double
  39. * @param number
  40. * @return
  41. */
  42. public static double toDouble(Number number){
  43. if(number instanceof Float){
  44. //float转double时,会出现精度问题。"(double)1.1f"的值类似于1.1000000476837158),
  45. //使用 Double.parseDouble(number.toString());则不会出现问题。
  46. return Double.parseDouble(number.toString());
  47. }
  48. return number.doubleValue();
  49. }

通过上面的returnMe = toDouble(r) + toDouble(value); 代码片段,我们就知道Fel是拿double进行加法操作的,这样会某些情况下就会产生精度问题。

其他操作同之。

解决办法:
避免使用浮点数进行数值计算,可以将操作数乘以10的某个倍数,将浮点数转成整数。至于从整数再转成浮点数就可以使用BigDecimal了。其实,一个好的财务系统都是不会存储和使用浮点数的,都是转成整数,只有在进行页面显示的时候才处理回浮点数。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注