精度問題:
我們知道java中直接使用float和double參與的計算都可能會產生精度問題,比如0.1+0.3、1.0-0.9 等。所以一般財務系統,都會使用BigDecimal進行加減乘除。 在調研Fel過程中,發現Fel里進行計算都是使用浮點數加減乘除的,所以不可避免的會產生精度問題。
Case+源碼分析:
加法 Case:
FelEngine fel = new FelEngineImpl();
Object result = fel.eval("0.1+0.2");
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計算的。
public Object call(FelNode node, FelContext context) {
Object returnMe = null;
for (Iterator<FelNode> iterator = node.getChildren().iterator(); iterator.hasNext();) {
Object child = iterator.next();
if (child instanceof FelNode) {
FelNode childNode = (FelNode) child;
child = childNode.eval(context);
}
if (child instanceof String) {
if (returnMe == null) {
returnMe = child;
continue;
}
returnMe = returnMe + (String) child;
}
if (child instanceof Number) {
if (returnMe == null) {
returnMe = child;
continue;
}
Number value = (Number) child;
if (returnMe instanceof Number) {
Number r = (Number) returnMe;
// 注意這里:是直接使用轉成double進行加減的。
returnMe = toDouble(r) + toDouble(value);
}else if(returnMe instanceof String){
String r = (String) returnMe;
returnMe=r+value;
}
}
}
if(returnMe instanceof Number){
return NumberUtil.parseNumber(returnMe.toString());
}
return returnMe;
}
/**
* 將Number轉換成double
* @param number
* @return
*/
public static double toDouble(Number number){
if(number instanceof Float){
//float轉double時,會出現精度問題。"(double)1.1f"的值類似於1.1000000476837158),
//使用 Double.parseDouble(number.toString());則不會出現問題。
return Double.parseDouble(number.toString());
}
return number.doubleValue();
}
通過上面的returnMe = toDouble(r) + toDouble(value);
代碼片段,我們就知道Fel是拿double進行加法操作的,這樣某些情況下就會產生精度問題。
其他運算操作同之。
解決辦法:
避免使用浮點數進行數值計算,可以將操作數乘以10的某個倍數,將浮點數轉成整數。至於從整數再轉成浮點數就可以使用BigDecimal了。其實,一個好的財務系統都是不會存儲和使用浮點數的,都是轉成整數,只有在進行頁面顯示的時候才處理回浮點數。