diff options
author | Hans Boehm <hboehm@google.com> | 2015-10-10 00:18:41 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-10-10 00:18:41 +0000 |
commit | 1a78db27efd1814e00b3a1af620a3a6900f60287 (patch) | |
tree | fdf85a6f1f752d07ccdd822d9fb32fc584d5f04e /src/com/android/calculator2/CalculatorExpr.java | |
parent | 0f3d460e948ba8fce7a72c74ee05458bb431ee68 (diff) | |
parent | 492d496dc01b452973b05ffd3c11a33d2e86bd5b (diff) | |
download | android_packages_apps_ExactCalculator-1a78db27efd1814e00b3a1af620a3a6900f60287.tar.gz android_packages_apps_ExactCalculator-1a78db27efd1814e00b3a1af620a3a6900f60287.tar.bz2 android_packages_apps_ExactCalculator-1a78db27efd1814e00b3a1af620a3a6900f60287.zip |
am 492d496d: am b13daf10: Evaluator.java and CalculatorExpr.java cleanup
* commit '492d496dc01b452973b05ffd3c11a33d2e86bd5b':
Evaluator.java and CalculatorExpr.java cleanup
Diffstat (limited to 'src/com/android/calculator2/CalculatorExpr.java')
-rw-r--r-- | src/com/android/calculator2/CalculatorExpr.java | 702 |
1 files changed, 395 insertions, 307 deletions
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java index 8a008b8..b387e8b 100644 --- a/src/com/android/calculator2/CalculatorExpr.java +++ b/src/com/android/calculator2/CalculatorExpr.java @@ -36,9 +36,19 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; -// A mathematical expression represented as a sequence of "tokens". -// Many tokes are represented by button ids for the corresponding operator. -// Parsed only when we evaluate the expression using the "eval" method. +/** + * A mathematical expression represented as a sequence of "tokens". + * Many tokens are represented by button ids for the corresponding operator. + * A token may also represent the result of a previously evaluated expression. + * The add() method adds a token to the end of the expression. The delete method() removes one. + * Clear() deletes the entire expression contents. Eval() evaluates the expression, + * producing both a constructive real (CR), and possibly a BoundedRational result. + * Expressions are parsed only during evaluation; no explicit parse tree is maintained. + * + * The write() method is used to save the current expression. Note that CR provides no + * serialization facility. Thus we save all previously computed values by writing out the + * expression that was used to compute them, and reevaluate on input. + */ class CalculatorExpr { private ArrayList<Token> mExpr; // The actual representation // as a list of tokens. Constant @@ -66,41 +76,45 @@ class CalculatorExpr { abstract CharSequence toCharSequence(Context context); } - // An operator token + /** + * Representation of an operator token + */ private static class Operator extends Token { - final int mId; // We use the button resource id + public final int id; // We use the button resource id Operator(int resId) { - mId = resId; + id = resId; } Operator(DataInput in) throws IOException { - mId = in.readInt(); + id = in.readInt(); } @Override void write(DataOutput out) throws IOException { out.writeByte(TokenKind.OPERATOR.ordinal()); - out.writeInt(mId); + out.writeInt(id); } @Override public CharSequence toCharSequence(Context context) { - String desc = KeyMaps.toDescriptiveString(context, mId); + String desc = KeyMaps.toDescriptiveString(context, id); if (desc != null) { - SpannableString result = new SpannableString(KeyMaps.toString(context, mId)); + SpannableString result = new SpannableString(KeyMaps.toString(context, id)); Object descSpan = new TtsSpan.TextBuilder(desc).build(); result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); return result; } else { - return KeyMaps.toString(context, mId); + return KeyMaps.toString(context, id); } } @Override TokenKind kind() { return TokenKind.OPERATOR; } } - // A (possibly incomplete) numerical constant. - // Supports addition and removal of trailing characters; hence mutable. + /** + * Representation of a (possibly incomplete) numerical constant. + * Supports addition and removal of trailing characters; hence mutable. + */ private static class Constant extends Token implements Cloneable { private boolean mSawDecimal; - String mWhole; // String preceding decimal point. + private String mWhole; // String preceding decimal point. private String mFraction; // String after decimal point. private int mExponent; // Explicit exponent, only generated through addExponent. @@ -132,7 +146,7 @@ class CalculatorExpr { // Just return false if this was the second (or later) decimal point // in this constant. // Assumes that this constant does not have an exponent. - boolean add(int id) { + public boolean add(int id) { if (id == R.id.dec_point) { if (mSawDecimal || mExponent != 0) return false; mSawDecimal = true; @@ -159,14 +173,16 @@ class CalculatorExpr { return true; } - void addExponent(int exp) { + public void addExponent(int exp) { // Note that adding a 0 exponent is a no-op. That's OK. mExponent = exp; } - // Undo the last add. - // Assumes the constant is nonempty. - void delete() { + /** + * Undo the last add or remove last exponent digit. + * Assumes the constant is nonempty. + */ + public void delete() { if (mExponent != 0) { mExponent /= 10; // Once zero, it can only be added back with addExponent. @@ -179,12 +195,14 @@ class CalculatorExpr { } } - boolean isEmpty() { + public boolean isEmpty() { return (mSawDecimal == false && mWhole.isEmpty()); } - // Produces human-readable string, as typed. - // Result is internationalized. + /** + * Produce human-readable string representation of constant, as typed. + * Result is internationalized. + */ @Override public String toString() { String result = mWhole; @@ -198,7 +216,10 @@ class CalculatorExpr { return KeyMaps.translateResult(result); } - // Return non-null BoundedRational representation. + /** + * Return BoundedRational representation of constant. + * Never null. + */ public BoundedRational toRational() { String whole = mWhole; if (whole.isEmpty()) whole = "0"; @@ -214,73 +235,75 @@ class CalculatorExpr { } @Override - CharSequence toCharSequence(Context context) { + public CharSequence toCharSequence(Context context) { return toString(); } @Override - TokenKind kind() { return TokenKind.CONSTANT; } + public TokenKind kind() { + return TokenKind.CONSTANT; + } // Override clone to make it public @Override public Object clone() { - Constant res = new Constant(); - res.mWhole = mWhole; - res.mFraction = mFraction; - res.mSawDecimal = mSawDecimal; - res.mExponent = mExponent; - return res; + Constant result = new Constant(); + result.mWhole = mWhole; + result.mFraction = mFraction; + result.mSawDecimal = mSawDecimal; + result.mExponent = mExponent; + return result; } } - // Hash maps used to detect duplicate subexpressions when - // we write out CalculatorExprs and read them back in. + // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and + // read them back in. private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap = - new ThreadLocal<IdentityHashMap<CR,Integer>>(); + new ThreadLocal<IdentityHashMap<CR,Integer>>(); // Maps expressions to indices on output private static final ThreadLocal<HashMap<Integer,PreEval>>inMap = - new ThreadLocal<HashMap<Integer,PreEval>>(); + new ThreadLocal<HashMap<Integer,PreEval>>(); // Maps expressions to indices on output - private static final ThreadLocal<Integer> exprIndex = - new ThreadLocal<Integer>(); + private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>(); - static void initExprOutput() { + /** + * Prepare for expression output. + * Initializes map that will lbe used to avoid duplicating shared subexpressions. + * This avoids a potential exponential blow-up in the expression size. + */ + public static void initExprOutput() { outMap.set(new IdentityHashMap<CR,Integer>()); exprIndex.set(Integer.valueOf(0)); } - static void initExprInput() { + /** + * Prepare for expression input. + * Initializes map that will be used to reconstruct shared subexpressions. + */ + public static void initExprInput() { inMap.set(new HashMap<Integer,PreEval>()); } - // We treat previously evaluated subexpressions as tokens - // These are inserted when either: - // - We continue an expression after evaluating some of it. - // - TODO: When we copy/paste expressions. - // The representation includes three different representations - // of the expression: - // 1) The CR value for use in computation. - // 2) The integer value for use in the computations, - // if the expression evaluates to an integer. - // 3a) The corresponding CalculatorExpr, together with - // 3b) The context (currently just deg/rad mode) used to evaluate - // the expression. - // 4) A short string representation that is used to - // Display the expression. - // - // (3) is present only so that we can persist the object. - // (4) is stored explicitly to avoid waiting for recomputation in the UI - // thread. + /** + * The "token" class for previously evaluated subexpressions. + * We treat previously evaluated subexpressions as tokens. These are inserted when we either + * continue an expression after evaluating some of it, or copy an expression and paste it back + * in. + * The representation includes both CR and possibly BoundedRational values. In order to + * support saving and restoring, we also include the underlying expression itself, and the + * context (currently just degree mode) used to evaluate it. The short string representation + * is also stored in order to avoid potentially expensive recomputation in the UI thread. + */ private static class PreEval extends Token { - final CR mValue; - final BoundedRational mRatValue; + public final CR value; + public final BoundedRational ratValue; private final CalculatorExpr mExpr; private final EvalContext mContext; private final String mShortRep; // Not internationalized. PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr, EvalContext ec, String shortRep) { - mValue = val; - mRatValue = ratVal; + value = val; + ratValue = ratVal; mExpr = expr; mContext = ec; mShortRep = shortRep; @@ -293,13 +316,13 @@ class CalculatorExpr { // The parameter hash map maps expressions we've seen // before to their index. @Override - void write(DataOutput out) throws IOException { + public void write(DataOutput out) throws IOException { out.writeByte(TokenKind.PRE_EVAL.ordinal()); - Integer index = outMap.get().get(mValue); + Integer index = outMap.get().get(value); if (index == null) { int nextIndex = exprIndex.get() + 1; exprIndex.set(nextIndex); - outMap.get().put(mValue, nextIndex); + outMap.get().put(value, nextIndex); out.writeInt(nextIndex); mExpr.write(out); mContext.write(out); @@ -315,13 +338,10 @@ class CalculatorExpr { if (prev == null) { mExpr = new CalculatorExpr(in); mContext = new EvalContext(in, mExpr.mExpr.size()); - // Recompute other fields - // We currently do this in the UI thread, but we - // only create PreEval expressions that were - // previously successfully evaluated, and thus - // don't diverge. We also only evaluate to a - // constructive real, which involves substantial - // work only in fairly contrived circumstances. + // Recompute other fields We currently do this in the UI thread, but we only + // create PreEval expressions that were previously successfully evaluated, and + // thus don't diverge. We also only evaluate to a constructive real, which + // involves substantial work only in fairly contrived circumstances. // TODO: Deal better with slow evaluations. EvalRet res = null; try { @@ -331,32 +351,35 @@ class CalculatorExpr { // expressions that can be evaluated. Log.e("Calculator", "Unexpected syntax exception" + e); } - mValue = res.mVal; - mRatValue = res.mRatVal; + value = res.val; + ratValue = res.ratVal; mShortRep = in.readUTF(); inMap.get().put(index, this); } else { - mValue = prev.mValue; - mRatValue = prev.mRatValue; + value = prev.value; + ratValue = prev.ratValue; mExpr = prev.mExpr; mContext = prev.mContext; mShortRep = prev.mShortRep; } } @Override - CharSequence toCharSequence(Context context) { + public CharSequence toCharSequence(Context context) { return KeyMaps.translateResult(mShortRep); } @Override - TokenKind kind() { + public TokenKind kind() { return TokenKind.PRE_EVAL; } - boolean hasEllipsis() { + public boolean hasEllipsis() { return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1; } } - static Token newToken(DataInput in) throws IOException { + /** + * Read token from in. + */ + public static Token newToken(DataInput in) throws IOException { TokenKind kind = tokenKindValues[in.readByte()]; switch(kind) { case CONSTANT: @@ -377,6 +400,9 @@ class CalculatorExpr { mExpr = expr; } + /** + * Construct CalculatorExpr, by reading it from in. + */ CalculatorExpr(DataInput in) throws IOException { mExpr = new ArrayList<Token>(); int size = in.readInt(); @@ -385,7 +411,10 @@ class CalculatorExpr { } } - void write(DataOutput out) throws IOException { + /** + * Write this expression to out. + */ + public void write(DataOutput out) throws IOException { int size = mExpr.size(); out.writeInt(size); for (int i = 0; i < size; ++i) { @@ -393,6 +422,10 @@ class CalculatorExpr { } } + /** + * Does this expression end with a numeric constant? + * As opposed to an operator or preevaluated expression. + */ boolean hasTrailingConstant() { int s = mExpr.size(); if (s == 0) { @@ -402,13 +435,16 @@ class CalculatorExpr { return t instanceof Constant; } + /** + * Does this expression end with a binary operator? + */ private boolean hasTrailingBinary() { int s = mExpr.size(); if (s == 0) return false; Token t = mExpr.get(s-1); if (!(t instanceof Operator)) return false; Operator o = (Operator)t; - return (KeyMaps.isBinary(o.mId)); + return (KeyMaps.isBinary(o.id)); } /** @@ -420,10 +456,10 @@ class CalculatorExpr { */ boolean add(int id) { int s = mExpr.size(); - int d = KeyMaps.digVal(id); - boolean binary = KeyMaps.isBinary(id); + final int d = KeyMaps.digVal(id); + final boolean binary = KeyMaps.isBinary(id); Token lastTok = s == 0 ? null : mExpr.get(s-1); - int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).mId : 0; + int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0; // Quietly replace a trailing binary operator with another one, unless the second // operator is minus, in which case we just allow it as a unary minus. if (binary && !KeyMaps.isPrefix(id)) { @@ -436,7 +472,7 @@ class CalculatorExpr { } // s invalid and not used below. } - boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point); + final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point); if (isConstPiece) { // Since we treat juxtaposition as multiplication, a constant can appear anywhere. if (s == 0) { @@ -476,35 +512,37 @@ class CalculatorExpr { void removeTrailingAdditiveOperators() { while (true) { int s = mExpr.size(); - if (s == 0) break; + if (s == 0) { + break; + } Token lastTok = mExpr.get(s-1); - if (!(lastTok instanceof Operator)) break; - int lastOp = ((Operator) lastTok).mId; - if (lastOp != R.id.op_add && lastOp != R.id.op_sub) break; + if (!(lastTok instanceof Operator)) { + break; + } + int lastOp = ((Operator) lastTok).id; + if (lastOp != R.id.op_add && lastOp != R.id.op_sub) { + break; + } delete(); } } - // Append the contents of the argument expression. - // It is assumed that the argument expression will not change, - // and thus its pieces can be reused directly. - // TODO: We probably only need this for expressions consisting of - // a single PreEval "token", and may want to check that. - void append(CalculatorExpr expr2) { - // Check that we're not concatenating Constant or PreEval - // tokens, since the result would look like a single constant + /** + * Append the contents of the argument expression. + * It is assumed that the argument expression will not change, and thus its pieces can be + * reused directly. + */ + public void append(CalculatorExpr expr2) { int s = mExpr.size(); int s2 = expr2.mExpr.size(); - // Check that we're not concatenating Constant or PreEval - // tokens, since the result would look like a single constant, - // with very mysterious results for the user. + // Check that we're not concatenating Constant or PreEval tokens, since the result would + // look like a single constant, with very mysterious results for the user. if (s != 0 && s2 != 0) { Token last = mExpr.get(s-1); Token first = expr2.mExpr.get(0); if (!(first instanceof Operator) && !(last instanceof Operator)) { - // Fudge it by adding an explicit multiplication. - // We would have interpreted it as such anyway, and this - // makes it recognizable to the user. + // Fudge it by adding an explicit multiplication. We would have interpreted it as + // such anyway, and this makes it recognizable to the user. mExpr.add(new Operator(R.id.op_mul)); } } @@ -513,83 +551,97 @@ class CalculatorExpr { } } - // Undo the last key addition, if any. - void delete() { - int s = mExpr.size(); - if (s == 0) return; + /** + * Undo the last key addition, if any. + * Or possibly remove a trailing exponent digit. + */ + public void delete() { + final int s = mExpr.size(); + if (s == 0) { + return; + } Token last = mExpr.get(s-1); if (last instanceof Constant) { Constant c = (Constant)last; c.delete(); - if (!c.isEmpty()) return; + if (!c.isEmpty()) { + return; + } } mExpr.remove(s-1); } - void clear() { + /** + * Remove all tokens from the expression. + */ + public void clear() { mExpr.clear(); } - boolean isEmpty() { + public boolean isEmpty() { return mExpr.isEmpty(); } - // Returns a logical deep copy of the CalculatorExpr. - // Operator and PreEval tokens are immutable, and thus - // aren't really copied. + /** + * Returns a logical deep copy of the CalculatorExpr. + * Operator and PreEval tokens are immutable, and thus aren't really copied. + */ public Object clone() { - CalculatorExpr res = new CalculatorExpr(); + CalculatorExpr result = new CalculatorExpr(); for (Token t: mExpr) { if (t instanceof Constant) { - res.mExpr.add((Token)(((Constant)t).clone())); + result.mExpr.add((Token)(((Constant)t).clone())); } else { - res.mExpr.add(t); + result.mExpr.add(t); } } - return res; + return result; } // Am I just a constant? - boolean isConstant() { - if (mExpr.size() != 1) return false; + public boolean isConstant() { + if (mExpr.size() != 1) { + return false; + } return mExpr.get(0) instanceof Constant; } - // Return a new expression consisting of a single PreEval token - // representing the current expression. - // The caller supplies the value, degree mode, and short - // string representation, which must have been previously computed. - // Thus this is guaranteed to terminate reasonably quickly. - CalculatorExpr abbreviate(CR val, BoundedRational ratVal, + /** + * Return a new expression consisting of a single token representing the current pre-evaluated + * expression. + * The caller supplies the value, degree mode, and short string representation, which must + * have been previously computed. Thus this is guaranteed to terminate reasonably quickly. + */ + public CalculatorExpr abbreviate(CR val, BoundedRational ratVal, boolean dm, String sr) { CalculatorExpr result = new CalculatorExpr(); - Token t = new PreEval(val, ratVal, - new CalculatorExpr( - (ArrayList<Token>)mExpr.clone()), - new EvalContext(dm, mExpr.size()), sr); + Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()), + new EvalContext(dm, mExpr.size()), sr); result.mExpr.add(t); return result; } - // Internal evaluation functions return an EvalRet triple. - // We compute rational (BoundedRational) results when possible, both as - // a performance optimization, and to detect errors exactly when we can. - private class EvalRet { - int mPos; // Next position (expression index) to be parsed - final CR mVal; // Constructive Real result of evaluating subexpression - final BoundedRational mRatVal; // Exact Rational value or null if - // irrational or hard to compute. + /** + * Internal evaluation functions return an EvalRet triple. + * We compute rational (BoundedRational) results when possible, both as a performance + * optimization, and to detect errors exactly when we can. + */ + private static class EvalRet { + public int pos; // Next position (expression index) to be parsed. + public final CR val; // Constructive Real result of evaluating subexpression. + public final BoundedRational ratVal; // Exact Rational value or null. EvalRet(int p, CR v, BoundedRational r) { - mPos = p; - mVal = v; - mRatVal = r; + pos = p; + val = v; + ratVal = r; } } - // And take a context argument: + /** + * Internal evaluation functions take an EvalContext argument. + */ private static class EvalContext { - public final int mPrefixLength; // Length of prefix to evaluate. - // Not explicitly saved. + public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved. public final boolean mDegreeMode; // If we add any other kinds of evaluation modes, they go here. EvalContext(boolean degreeMode, int len) { @@ -625,22 +677,25 @@ class CalculatorExpr { } } - // The following methods can all throw IndexOutOfBoundsException - // in the event of a syntax error. We expect that to be caught in - // eval below. + // The following methods can all throw IndexOutOfBoundsException in the event of a syntax + // error. We expect that to be caught in eval below. private boolean isOperatorUnchecked(int i, int op) { Token t = mExpr.get(i); - if (!(t instanceof Operator)) return false; - return ((Operator)(t)).mId == op; + if (!(t instanceof Operator)) { + return false; + } + return ((Operator)(t)).id == op; } private boolean isOperator(int i, int op, EvalContext ec) { - if (i >= ec.mPrefixLength) return false; + if (i >= ec.mPrefixLength) { + return false; + } return isOperatorUnchecked(i, op); } - static class SyntaxException extends Exception { + public static class SyntaxException extends Exception { public SyntaxException() { super(); } @@ -649,27 +704,26 @@ class CalculatorExpr { } } - // The following functions all evaluate some kind of expression - // starting at position i in mExpr in a specified evaluation context. - // They return both the expression value (as constructive real and, - // if applicable, as BigInteger) and the position of the next token + // The following functions all evaluate some kind of expression starting at position i in + // mExpr in a specified evaluation context. They return both the expression value (as + // constructive real and, if applicable, as BoundedRational) and the position of the next token // that was not used as part of the evaluation. + // This is essentially a simple recursive descent parser combined with expression evaluation. + private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException { - Token t = mExpr.get(i); + final Token t = mExpr.get(i); BoundedRational ratVal; - CR value; if (t instanceof Constant) { Constant c = (Constant)t; ratVal = c.toRational(); - value = ratVal.CRValue(); - return new EvalRet(i+1, value, ratVal); + return new EvalRet(i+1, ratVal.CRValue(), ratVal); } if (t instanceof PreEval) { - PreEval p = (PreEval)t; - return new EvalRet(i+1, p.mValue, p.mRatValue); + final PreEval p = (PreEval)t; + return new EvalRet(i+1, p.value, p.ratValue); } EvalRet argVal; - switch(((Operator)(t)).mId) { + switch(((Operator)(t)).id) { case R.id.const_pi: return new EvalRet(i+1, CR.PI, null); case R.id.const_e: @@ -680,111 +734,144 @@ class CalculatorExpr { // Does seem to accept a leading minus. if (isOperator(i+1, R.id.op_sub, ec)) { argVal = evalUnary(i+2, ec); - ratVal = BoundedRational.sqrt( - BoundedRational.negate(argVal.mRatVal)); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - argVal.mVal.negate().sqrt(), null); + ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal)); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, + argVal.val.negate().sqrt(), null); } else { argVal = evalUnary(i+1, ec); - ratVal = BoundedRational.sqrt(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null); + ratVal = BoundedRational.sqrt(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, argVal.val.sqrt(), null); } case R.id.lparen: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + return new EvalRet(argVal.pos, argVal.val, argVal.ratVal); case R.id.fun_sin: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal) - : BoundedRational.sin(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - toRadians(argVal.mVal,ec).sin(), null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal) + : BoundedRational.sin(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null); case R.id.fun_cos: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal) - : BoundedRational.cos(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - toRadians(argVal.mVal,ec).cos(), null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal) + : BoundedRational.cos(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null); case R.id.fun_tan: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal) - : BoundedRational.tan(argVal.mRatVal); - if (ratVal != null) break; - CR argCR = toRadians(argVal.mVal, ec); - return new EvalRet(argVal.mPos, - argCR.sin().divide(argCR.cos()), null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal) + : BoundedRational.tan(argVal.ratVal); + if (ratVal != null) { + break; + } + CR argCR = toRadians(argVal.val, ec); + return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null); case R.id.fun_ln: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = BoundedRational.ln(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, argVal.mVal.ln(), null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = BoundedRational.ln(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, argVal.val.ln(), null); case R.id.fun_exp: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = BoundedRational.exp(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, argVal.mVal.exp(), null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = BoundedRational.exp(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, argVal.val.exp(), null); case R.id.fun_log: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = BoundedRational.log(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - argVal.mVal.ln().divide(CR.valueOf(10).ln()), - null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = BoundedRational.log(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null); case R.id.fun_arcsin: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal) - : BoundedRational.asin(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - fromRadians(UnaryCRFunction - .asinFunction.execute(argVal.mVal),ec), - null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal) + : BoundedRational.asin(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, + fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null); case R.id.fun_arccos: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal) - : BoundedRational.acos(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - fromRadians(UnaryCRFunction - .acosFunction.execute(argVal.mVal),ec), - null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal) + : BoundedRational.acos(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, + fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null); case R.id.fun_arctan: argVal = evalExpr(i+1, ec); - if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; - ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal) - : BoundedRational.atan(argVal.mRatVal); - if (ratVal != null) break; - return new EvalRet(argVal.mPos, - fromRadians(UnaryCRFunction - .atanFunction.execute(argVal.mVal),ec), - null); + if (isOperator(argVal.pos, R.id.rparen, ec)) { + argVal.pos++; + } + ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal) + : BoundedRational.atan(argVal.ratVal); + if (ratVal != null) { + break; + } + return new EvalRet(argVal.pos, + fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null); default: throw new SyntaxException("Unrecognized token in expression"); } // We have a rational value. - return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal); + return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal); } - // Compute an integral power of a constructive real. - // Unlike the "general" case using logarithms, this handles a negative - // base. + /** + * Compute an integral power of a constructive real. + * Unlike the "general" case using logarithms, this handles a negative base. + */ private static CR pow(CR base, BigInteger exp) { if (exp.compareTo(BigInteger.ZERO) < 0) { return pow(base, exp.negate()).inverse(); } - if (exp.equals(BigInteger.ONE)) return base; + if (exp.equals(BigInteger.ONE)) { + return base; + } if (exp.and(BigInteger.ONE).intValue() == 1) { return pow(base, exp.subtract(BigInteger.ONE)).multiply(base); } @@ -795,24 +882,23 @@ class CalculatorExpr { return tmp.multiply(tmp); } + // Number of bits past binary point to test for integer-ness. private static final int TEST_PREC = -100; - // Test for integer-ness to 100 bits past binary point. private static final BigInteger MASK = BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE); private static final CR REAL_E = CR.valueOf(1).exp(); private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse(); - private static final BoundedRational RATIONAL_ONE_HUNDREDTH = - new BoundedRational(1,100); + private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100); private static boolean isApprInt(CR x) { BigInteger appr = x.get_appr(TEST_PREC); return appr.and(MASK).signum() == 0; } private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException { - EvalRet tmp = evalUnary(i, ec); - int cpos = tmp.mPos; - CR cval = tmp.mVal; - BoundedRational ratVal = tmp.mRatVal; + final EvalRet tmp = evalUnary(i, ec); + int cpos = tmp.pos; + CR crVal = tmp.val; + BoundedRational ratVal = tmp.ratVal; boolean isFact; boolean isSquared = false; while ((isFact = isOperator(cpos, R.id.op_fact, ec)) || @@ -820,46 +906,45 @@ class CalculatorExpr { isOperator(cpos, R.id.op_pct, ec)) { if (isFact) { if (ratVal == null) { - // Assume it was an integer, but we - // didn't figure it out. + // Assume it was an integer, but we didn't figure it out. // KitKat may have used the Gamma function. - if (!isApprInt(cval)) { + if (!isApprInt(crVal)) { throw new ArithmeticException("factorial(non-integer)"); } - ratVal = new BoundedRational(cval.BigIntegerValue()); + ratVal = new BoundedRational(crVal.BigIntegerValue()); } ratVal = BoundedRational.fact(ratVal); - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } else if (isSquared) { ratVal = BoundedRational.multiply(ratVal, ratVal); if (ratVal == null) { - cval = cval.multiply(cval); + crVal = crVal.multiply(crVal); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } else /* percent */ { ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH); if (ratVal == null) { - cval = cval.multiply(REAL_ONE_HUNDREDTH); + crVal = crVal.multiply(REAL_ONE_HUNDREDTH); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } ++cpos; } - return new EvalRet(cpos, cval, ratVal); + return new EvalRet(cpos, crVal, ratVal); } private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException { final EvalRet result1 = evalSuffix(i, ec); - int cpos = result1.mPos; // current position - CR cval = result1.mVal; // value so far - BoundedRational ratVal = result1.mRatVal; // int value so far + int cpos = result1.pos; // current position + CR crVal = result1.val; // value so far + BoundedRational ratVal = result1.ratVal; // int value so far if (isOperator(cpos, R.id.op_pow, ec)) { - final EvalRet exp = evalSignedFactor(cpos+1, ec); - cpos = exp.mPos; + final EvalRet exp = evalSignedFactor(cpos + 1, ec); + cpos = exp.pos; // Try completely rational evaluation first. - ratVal = BoundedRational.pow(ratVal, exp.mRatVal); + ratVal = BoundedRational.pow(ratVal, exp.ratVal); if (ratVal != null) { return new EvalRet(cpos, ratVal.CRValue(), ratVal); } @@ -867,33 +952,33 @@ class CalculatorExpr { // Thus we handle that case separately. // We punt if the exponent is an integer computed from irrational // values. That wouldn't work reliably with floating point either. - BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal); + BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal); if (int_exp != null) { - cval = pow(cval, int_exp); + crVal = pow(crVal, int_exp); } else { - cval = cval.ln().multiply(exp.mVal).exp(); + crVal = crVal.ln().multiply(exp.val).exp(); } ratVal = null; } - return new EvalRet(cpos, cval, ratVal); + return new EvalRet(cpos, crVal, ratVal); } private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException { final boolean negative = isOperator(i, R.id.op_sub, ec); int cpos = negative ? i + 1 : i; EvalRet tmp = evalFactor(cpos, ec); - cpos = tmp.mPos; - CR cval = negative ? tmp.mVal.negate() : tmp.mVal; - BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal) - : tmp.mRatVal; - return new EvalRet(cpos, cval, ratVal); + cpos = tmp.pos; + CR crVal = negative ? tmp.val.negate() : tmp.val; + BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal) + : tmp.ratVal; + return new EvalRet(cpos, crVal, ratVal); } private boolean canStartFactor(int i) { if (i >= mExpr.size()) return false; Token t = mExpr.get(i); if (!(t instanceof Operator)) return true; - int id = ((Operator)(t)).mId; + int id = ((Operator)(t)).id; if (KeyMaps.isBinary(id)) return false; switch (id) { case R.id.op_fact: @@ -908,72 +993,74 @@ class CalculatorExpr { EvalRet tmp = evalSignedFactor(i, ec); boolean is_mul = false; boolean is_div = false; - int cpos = tmp.mPos; // Current position in expression. - CR cval = tmp.mVal; // Current value. - BoundedRational ratVal = tmp.mRatVal; // Current rational value. + int cpos = tmp.pos; // Current position in expression. + CR crVal = tmp.val; // Current value. + BoundedRational ratVal = tmp.ratVal; // Current rational value. while ((is_mul = isOperator(cpos, R.id.op_mul, ec)) || (is_div = isOperator(cpos, R.id.op_div, ec)) || canStartFactor(cpos)) { if (is_mul || is_div) ++cpos; tmp = evalSignedFactor(cpos, ec); if (is_div) { - ratVal = BoundedRational.divide(ratVal, tmp.mRatVal); + ratVal = BoundedRational.divide(ratVal, tmp.ratVal); if (ratVal == null) { - cval = cval.divide(tmp.mVal); + crVal = crVal.divide(tmp.val); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } else { - ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal); + ratVal = BoundedRational.multiply(ratVal, tmp.ratVal); if (ratVal == null) { - cval = cval.multiply(tmp.mVal); + crVal = crVal.multiply(tmp.val); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } - cpos = tmp.mPos; + cpos = tmp.pos; is_mul = is_div = false; } - return new EvalRet(cpos, cval, ratVal); + return new EvalRet(cpos, crVal, ratVal); } private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException { EvalRet tmp = evalTerm(i, ec); boolean is_plus; - int cpos = tmp.mPos; - CR cval = tmp.mVal; - BoundedRational ratVal = tmp.mRatVal; + int cpos = tmp.pos; + CR crVal = tmp.val; + BoundedRational ratVal = tmp.ratVal; while ((is_plus = isOperator(cpos, R.id.op_add, ec)) || isOperator(cpos, R.id.op_sub, ec)) { tmp = evalTerm(cpos+1, ec); if (is_plus) { - ratVal = BoundedRational.add(ratVal, tmp.mRatVal); + ratVal = BoundedRational.add(ratVal, tmp.ratVal); if (ratVal == null) { - cval = cval.add(tmp.mVal); + crVal = crVal.add(tmp.val); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } else { - ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal); + ratVal = BoundedRational.subtract(ratVal, tmp.ratVal); if (ratVal == null) { - cval = cval.subtract(tmp.mVal); + crVal = crVal.subtract(tmp.val); } else { - cval = ratVal.CRValue(); + crVal = ratVal.CRValue(); } } - cpos = tmp.mPos; + cpos = tmp.pos; } - return new EvalRet(cpos, cval, ratVal); + return new EvalRet(cpos, crVal, ratVal); } - // Externally visible evaluation result. - public class EvalResult { - EvalResult (CR val, BoundedRational ratVal) { - mVal = val; - mRatVal = ratVal; + /** + * Externally visible evaluation result. + */ + public static class EvalResult { + public final CR val; + public final BoundedRational ratVal; + EvalResult (CR v, BoundedRational rv) { + val = v; + ratVal = rv; } - final CR mVal; - final BoundedRational mRatVal; } /** @@ -985,13 +1072,15 @@ class CalculatorExpr { Token last = mExpr.get(result - 1); if (!(last instanceof Operator)) break; Operator o = (Operator)last; - if (!KeyMaps.isBinary(o.mId)) break; + if (!KeyMaps.isBinary(o.id)) break; --result; } return result; } - // Is the current expression worth evaluating? + /** + * Is the current expression worth evaluating? + */ public boolean hasInterestingOps() { int last = trailingBinaryOpsStart(); int first = 0; @@ -1011,9 +1100,9 @@ class CalculatorExpr { /** * Evaluate the expression excluding trailing binary operators. - * Errors result in exceptions, most of which are unchecked. - * Should not be called concurrently with modification of the expression. - * May take a very long time; avoid calling from UI thread. + * Errors result in exceptions, most of which are unchecked. Should not be called + * concurrently with modification of the expression. May take a very long time; avoid calling + * from UI thread. * * @param degreeMode use degrees rather than radians */ @@ -1022,18 +1111,17 @@ class CalculatorExpr { // and BoundedRational. { try { - // We currently never include trailing binary operators, but include - // other trailing operators. - // Thus we usually, but not always, display results for prefixes - // of valid expressions, and don't generate an error where we previously - // displayed an instant result. This reflects the Android L design. + // We currently never include trailing binary operators, but include other trailing + // operators. Thus we usually, but not always, display results for prefixes of valid + // expressions, and don't generate an error where we previously displayed an instant + // result. This reflects the Android L design. int prefixLen = trailingBinaryOpsStart(); EvalContext ec = new EvalContext(degreeMode, prefixLen); EvalRet res = evalExpr(0, ec); - if (res.mPos != prefixLen) { + if (res.pos != prefixLen) { throw new SyntaxException("Failed to parse full expression"); } - return new EvalResult(res.mVal, res.mRatVal); + return new EvalResult(res.val, res.ratVal); } catch (IndexOutOfBoundsException e) { throw new SyntaxException("Unexpected expression end"); } |