1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.calculator2; 18 19 import org.javia.arity.Symbols; 20 import org.javia.arity.SyntaxException; 21 import org.javia.arity.Util; 22 23 public class CalculatorExpressionEvaluator { 24 25 /** 26 * The maximum number of significant digits to display. 27 */ 28 private static final int MAX_DIGITS = 12; 29 30 /** 31 * A {@link Double} has at least 17 significant digits, we show the first {@link #MAX_DIGITS} 32 * and use the remaining digits as guard digits to hide floating point precision errors. 33 */ 34 private static final int ROUNDING_DIGITS = Math.max(17 - MAX_DIGITS, 0); 35 36 private final Symbols mSymbols; 37 private final CalculatorExpressionTokenizer mTokenizer; 38 39 public CalculatorExpressionEvaluator(CalculatorExpressionTokenizer tokenizer) { 40 mSymbols = new Symbols(); 41 mTokenizer = tokenizer; 42 } 43 44 public void evaluate(CharSequence expr, EvaluateCallback callback) { 45 evaluate(expr.toString(), callback); 46 } 47 48 public void evaluate(String expr, EvaluateCallback callback) { 49 expr = mTokenizer.getNormalizedExpression(expr); 50 51 // remove any trailing operators 52 while (expr.length() > 0 && "+-/*".indexOf(expr.charAt(expr.length() - 1)) != -1) { 53 expr = expr.substring(0, expr.length() - 1); 54 } 55 56 try { 57 if (expr.length() == 0 || Double.valueOf(expr) != null) { 58 callback.onEvaluate(expr, null, Calculator.INVALID_RES_ID); 59 return; 60 } 61 } catch (NumberFormatException e) { 62 // expr is not a simple number 63 } 64 65 try { 66 double result = mSymbols.eval(expr); 67 if (Double.isNaN(result)) { 68 callback.onEvaluate(expr, null, R.string.error_nan); 69 } else { 70 // The arity library uses floating point arithmetic when evaluating the expression 71 // leading to precision errors in the result. The method doubleToString hides these 72 // errors; rounding the result by dropping N digits of precision. 73 final String resultString = mTokenizer.getLocalizedExpression( 74 Util.doubleToString(result, MAX_DIGITS, ROUNDING_DIGITS)); 75 callback.onEvaluate(expr, resultString, Calculator.INVALID_RES_ID); 76 } 77 } catch (SyntaxException e) { 78 callback.onEvaluate(expr, null, R.string.error_syntax); 79 } 80 } 81 82 public interface EvaluateCallback { 83 public void onEvaluate(String expr, String result, int errorResourceId); 84 } 85 } 86