Evaluator.java and CalculatorExpr.java cleanup

Bug: 24811759
Bug: 21960281
Bug: 22047258

No substantive changes.

Reformat to 100 columns.

Correct and/or remove obsolete comments and occasional bits of clearly
dead code.

Better follow coding conventions.

Rename variables for both internal consistency and consistency with
other files.  This included a few renamings in Calculator.java
and CalculatorResult.java, and a comment fix in CalculatorResult.java.

Rename public fields per coding convention.  Correctly declare more
nested classes static.

Document FIXME issues to be addressed in followup CL.  This includes
a careful look at the msd computations.  I cannot construct any
failing examples, but I also cannot currently construct a correctness
argument.

Change-Id: I5c67493eeb7730edb4b3ca3ba1cb8b7d2b87dbc2
(cherry picked from commit abe2862bc936dd083b5ba19b68c68ea4cc44b2f6)
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 4eecb50..035f5b9 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -991,7 +991,7 @@
                 setState(CalculatorState.INPUT);
                 mEvaluator.clear();
             }
-            mEvaluator.addSaved();
+            mEvaluator.appendSaved();
             redisplayAfterFormulaChange();
         } else {
             addChars(item.coerceToText(this).toString(), false);
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.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 @@
         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 @@
         // 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 @@
             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 @@
             }
         }
 
-        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 @@
             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 @@
         }
 
         @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 @@
         // 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 @@
             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 @@
                     // 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 @@
         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 @@
         }
     }
 
-    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 @@
         }
     }
 
+    /**
+     * 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 @@
         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 @@
      */
     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 @@
             }
             // 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 @@
     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 @@
         }
     }
 
-    // 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 @@
         }
     }
 
-    // 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 @@
         }
     }
 
-    // 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 @@
             // 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 @@
         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 @@
                 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 @@
             // 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 @@
         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 @@
             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 @@
 
     /**
      * 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 @@
                         // 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");
         }
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 1efa67b..289e13d 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -335,9 +335,9 @@
 
     /*
      * Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
-     * Unlike Evaluator.getMsdPos, we treat a final 1 as significant.
+     * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
      */
-    public static int getNaiveMsdIndex(String s) {
+    public static int getNaiveMsdIndexOf(String s) {
         int len = s.length();
         for (int i = 0; i < len; ++i) {
             char c = s.charAt(i);
@@ -349,14 +349,14 @@
     }
 
     // Format a result returned by Evaluator.getString() into a single line containing ellipses
-    // (if appropriate) and an exponent (if appropriate).  prec is the value that was passed to
-    // getString and thus identifies the significance of the rightmost digit.
+    // (if appropriate) and an exponent (if appropriate).  precOffset is the value that was passed
+    // to getString and thus identifies the significance of the rightmost digit.
     // A value of 1 means the rightmost digits corresponds to tenths.
     // maxDigs is the maximum number of characters in the result.
     // We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in
     // the display.
     // If forcePrecision is true, we make sure that the last displayed digit corresponds to
-    // prec, and allow maxDigs to be exceeded in assing the exponent.
+    // precOffset, and allow maxDigs to be exceeded in assing the exponent.
     // We add two distinct kinds of exponents:
     // (1) If the final result contains the leading digit we use standard scientific notation.
     // (2) If not, we add an exponent corresponding to an interpretation of the final result as
@@ -369,7 +369,7 @@
     public String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
             boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
         final int minusSpace = negative ? 1 : 0;
-        final int msdIndex = truncated ? -1 : getNaiveMsdIndex(in);  // INVALID_MSD is OK.
+        final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in);  // INVALID_MSD is OK.
         final int decIndex = in.indexOf('.');
         String result = in;
         lastDisplayedOffset[0] = precOffset;
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index b779a68..e1bcaf6 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -14,68 +14,6 @@
  * limitations under the License.
  */
 
-//
-// This implements the calculator evaluation logic.
-// An evaluation is started with a call to evaluateAndShowResult().
-// This starts an asynchronous computation, which requests display
-// of the initial result, when available.  When initial evaluation is
-// complete, it calls the calculator onEvaluate() method.
-// This occurs in a separate event, and may happen quite a bit
-// later.  Once a result has been computed, and before the underlying
-// expression is modified, the getString method may be used to produce
-// Strings that represent approximations to various precisions.
-//
-// Actual expressions being evaluated are represented as CalculatorExprs,
-// which are just slightly preprocessed sequences of keypresses.
-//
-// The Evaluator owns the expression being edited and associated
-// state needed for evaluating it. It provides functionality for
-// saving and restoring this state.  However the current
-// CalculatorExpr is exposed to the client, and may be directly modified
-// after cancelling any in-progress computations by invoking the
-// cancelAll() method.
-//
-// When evaluation is requested by the user, we invoke the eval
-// method on the CalculatorExpr from a background AsyncTask.
-// A subsequent getString() callback returns immediately, though it may
-// return a result containing placeholder '?' characters.
-// In that case we start a background task, which invokes the
-// onReevaluate() callback when it completes.
-// In both cases, the background task
-// computes the appropriate result digits by evaluating
-// the constructive real (CR) returned by CalculatorExpr.eval()
-// to the required precision.
-//
-// We cache the best approximation we have already computed.
-// We compute generously to allow for
-// some scrolling without recomputation and to minimize the chance of
-// digits flipping from "0000" to "9999".  The best known
-// result approximation is maintained as a string by mCache (and
-// in a different format by the CR representation of the result).
-// When we are in danger of not having digits to display in response
-// to further scrolling, we initiate a background computation to higher
-// precision.  If we actually do fall behind, we display placeholder
-// characters, e.g. blanks, and schedule a display update when the computation
-// completes.
-// The code is designed to ensure that the error in the displayed
-// result (excluding any placeholder characters) is always strictly less than 1 in
-// the last displayed digit.  Typically we actually display a prefix
-// of a result that has this property and additionally is computed to
-// a significantly higher precision.  Thus we almost always round correctly
-// towards zero.  (Fully correct rounding towards zero is not computable.)
-//
-// Initial expression evaluation may time out.  This may happen in the
-// case of domain errors such as division by zero, or for large computations.
-// We do not currently time out reevaluations to higher precision, since
-// the original evaluation prevcluded a domain error that could result
-// in non-termination.  (We may discover that a presumed zero result is
-// actually slightly negative when re-evaluated; but that results in an
-// exception, which we can handle.)  The user can abort either kind
-// of computation.
-//
-// We ensure that only one evaluation of either kind (AsyncReevaluator
-// or AsyncDisplayResult) is running at a time.
-
 package com.android.calculator2;
 
 import android.app.AlertDialog;
@@ -100,102 +38,145 @@
 import java.util.Random;
 import java.util.TimeZone;
 
+/**
+ * This implements the calculator evaluation logic.  The underlying expression is constructed and
+ * edited with append(), delete(), and clear().  An evaluation an then be started with a call to
+ * evaluateAndShowResult() or requireResult().  This starts an asynchronous computation, which
+ * requests display of the initial result, when available.  When initial evaluation is complete,
+ * it calls the calculator onEvaluate() method.  This occurs in a separate event, possibly quite a
+ * bit later.  Once a result has been computed, and before the underlying expression is modified,
+ * the getString() method may be used to produce Strings that represent approximations to various
+ * precisions.
+ *
+ * Actual expressions being evaluated are represented as {@link CalculatorExpr}s.
+ *
+ * The Evaluator owns the expression being edited and all associated state needed for evaluating
+ * it.  It provides functionality for saving and restoring this state.  However the current
+ * CalculatorExpr is exposed to the client, and may be directly accessed after cancelling any
+ * in-progress computations by invoking the cancelAll() method.
+ *
+ * When evaluation is requested, we invoke the eval() method on the CalculatorExpr from a
+ * background AsyncTask.  A subsequent getString() callback returns immediately, though it may
+ * return a result containing placeholder ' ' characters.  If we had to return palceholder
+ * characters, we start a background task, which invokes the onReevaluate() callback when it
+ * completes.  In either case, the background task computes the appropriate result digits by
+ * evaluating the constructive real (CR) returned by CalculatorExpr.eval() to the required
+ * precision.
+ *
+ * We cache the best decimal approximation we have already computed.  We compute generously to
+ * allow for some scrolling without recomputation and to minimize the chance of digits flipping
+ * from "0000" to "9999".  The best known result approximation is maintained as a string by
+ * mResultString (and in a different format by the CR representation of the result).  When we are
+ * in danger of not having digits to display in response to further scrolling, we also initiate a
+ * background computation to higher precision, as if we had generated placeholder characters.
+ *
+ * The code is designed to ensure that the error in the displayed result (excluding any
+ * placeholder characters) is always strictly less than 1 in the last displayed digit.  Typically
+ * we actually display a prefix of a result that has this property and additionally is computed to
+ * a significantly higher precision.  Thus we almost always round correctly towards zero.  (Fully
+ * correct rounding towards zero is not computable, at least given our representation.)
+ *
+ * Initial expression evaluation may time out.  This may happen in the case of domain errors such
+ * as division by zero, or for large computations.  We do not currently time out reevaluations to
+ * higher precision, since the original evaluation precluded a domain error that could result in
+ * non-termination.  (We may discover that a presumed zero result is actually slightly negative
+ * when re-evaluated; but that results in an exception, which we can handle.)  The user can abort
+ * either kind of computation.
+ *
+ * We ensure that only one evaluation of either kind (AsyncEvaluator or AsyncReevaluator) is
+ * running at a time.
+ */
 class Evaluator {
 
+    // When naming variables and fields, "Offset" denotes a character offset in a string
+    // representing a decimal number, where the offset is relative to the decimal point.  1 =
+    // tenths position, -1 = units position.  Integer.MAX_VALUE is sometimes used for the offset
+    // of the last digit in an a nonterminating decimal expansion.  We use the suffix "Index" to
+    // denote a zero-based absolute index into such a string.
+
     private static final String KEY_PREF_DEGREE_MODE = "degree_mode";
 
-    private final Calculator mCalculator;
-    private final CalculatorResult mResult;  // The result display View
-    private CalculatorExpr mExpr;      // Current calculator expression
-    private CalculatorExpr mSaved;     // Last saved expression.
-                                       // Either null or contains a single
-                                       // preevaluated node.
-    private String mSavedName;         // A hopefully unique name associated
-                                       // with mSaved.
-    // The following are valid only if an evaluation
-    // completed successfully.
-        private CR mVal;               // value of mExpr as constructive real
-        private BoundedRational mRatVal; // value of mExpr as rational or null
-        private int mLastDigs;   // Last digit argument passed to getString()
-                                 // for this result, or the initial preferred
-                                 // precision.
-    private boolean mDegreeMode;       // Currently in degree (not radian) mode
-    private final Handler mTimeoutHandler;
-
-    static final BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
-
+    // The minimum number of extra digits we always try to compute to improve the chance of
+    // producing a correctly-rounded-towards-zero result.  The extra digits can be displayed to
+    // avoid generating placeholder digits, but should only be displayed briefly while computing.
     private static final int EXTRA_DIGITS = 20;
-                // Extra computed digits to minimize probably we will have
-                // to change our minds about digits we already displayed.
-                // (The correct digits are technically not computable using our
-                // representation:  An off by one error in the last digits
-                // can affect earlier ones, even though the display is
-                // always within one in the lsd.  This is only visible
-                // for results that end in EXTRA_DIGITS 9s or 0s, but are
-                // not integers.)
-                // We do use these extra digits to display while we are
-                // computing the correct answer.  Thus they may be
-                // temporarily visible.
-   private static final int EXTRA_DIVISOR = 5;
-                // We add the length of the previous result divided by
-                // EXTRA_DIVISOR to try to recover recompute latency when
-                // scrolling through a long result.
-   private static final int PRECOMPUTE_DIGITS = 30;
-   private static final int PRECOMPUTE_DIVISOR = 5;
-                // When we have to reevaluate, we compute an extra
-                // PRECOMPUTE_DIGITS
-                // + <current_result_length>/PRECOMPUTE_DIVISOR digits.
-                // The last term is dropped if prec < 0.
 
-    // We cache the result as a string to accelerate scrolling.
-    // The cache is filled in by the UI thread, but this may
-    // happen asynchronously, much later than the request.
-    private String mCache;       // Current best known result, which includes
-    private int mCacheDigs = 0;  // mCacheDigs digits to the right of the
-                                 // decimal point.  Always positive.
-                                 // mCache is valid when non-null
-                                 // unless the expression has been
-                                 // changed since the last evaluation call.
-    private int mCacheDigsReq;  // Number of digits that have been
-                                // requested.  Only touched by UI
-                                // thread.
-    public static final int INVALID_MSD = Integer.MAX_VALUE;
-    private int mMsd = INVALID_MSD;  // Position of most significant digit
-                                     // in current cached result, if determined.
-                                     // This is just the index in mCache
-                                     // holding the msd.
+    // We adjust EXTRA_DIGITS by adding the length of the previous result divided by
+    // EXTRA_DIVISOR.  This helps hide recompute latency when long results are requested;
+    // We start the recomputation substantially before the need is likely to be visible.
+    private static final int EXTRA_DIVISOR = 5;
+
+    // In addition to insisting on extra digits (see above), we minimize reevaluation
+    // frequency by precomputing an extra PRECOMPUTE_DIGITS
+    // + <current_precision_offset>/PRECOMPUTE_DIVISOR digits, whenever we are forced to
+    // reevaluate.  The last term is dropped if prec < 0.
+    private static final int PRECOMPUTE_DIGITS = 30;
+    private static final int PRECOMPUTE_DIVISOR = 5;
+
+    // Initial evaluation precision.  Enough to guarantee that we can compute the short
+    // representation, and that we rarely have to evaluate nonzero results to MAX_MSD_PREC_OFFSET.
+    // It also helps if this is at least EXTRA_DIGITS + display width, so that we don't
+    // immediately need a second evaluation.
     private static final int INIT_PREC = 50;
-                             // Initial evaluation precision.  Enough to guarantee
-                             // that we can compute the short representation, and that
-                             // we rarely have to evaluate nonzero results to
-                             // MAX_MSD_PREC.  It also helps if this is at least
-                             // EXTRA_DIGITS + display width, so that we don't
-                             // immediately need a second evaluation.
-    private static final int MAX_MSD_PREC = 320;
-                             // The largest number of digits to the right
-                             // of the decimal point to which we will
-                             // evaluate to compute proper scientific
-                             // notation for values close to zero.
-                             // Chosen to ensure that we always to better than
-                             // IEEE double precision at identifying nonzeros.
+
+    // The largest number of digits to the right of the decimal point to which we will evaluate to
+    // compute proper scientific notation for values close to zero.  Chosen to ensure that we
+    // always to better than IEEE double precision at identifying nonzeros.
+    private static final int MAX_MSD_PREC_OFFSET = 320;
+
+    // If we can replace an exponent by this many leading zeroes, we do so.  Also used in
+    // estimating exponent size for truncating short representation.
     private static final int EXP_COST = 3;
-                             // If we can replace an exponent by this many leading zeroes,
-                             // we do so.  Also used in estimating exponent size for
-                             // truncating short representation.
 
-    private AsyncReevaluator mCurrentReevaluator;
-        // The one and only un-cancelled and currently running reevaluator.
-        // Touched only by UI thread.
+    private final Calculator mCalculator;
+    private final CalculatorResult mResult;
 
-    private AsyncDisplayResult mEvaluator;
-        // Currently running expression evaluator, if any.
+    // The current caluclator expression.
+    private CalculatorExpr mExpr;
 
+    // Last saved expression.  Either null or contains a single CalculatorExpr.PreEval node.
+    private CalculatorExpr mSaved;
+
+    //  A hopefully unique name associated with mSaved.
+    private String mSavedName;
+
+    // The expression may have changed since the last evaluation in ways that would affect its
+    // value.
     private boolean mChangedValue;
-        // The expression may have changed since the last evaluation in ways that would
-        // affect its value.
 
     private SharedPreferences mSharedPrefs;
 
+    private boolean mDegreeMode;       // Currently in degree (not radian) mode.
+
+    private final Handler mTimeoutHandler;  // Used to schedule evaluation timeouts.
+
+    // The following are valid only if an evaluation completed successfully.
+        private CR mVal;               // Value of mExpr as constructive real.
+        private BoundedRational mRatVal; // Value of mExpr as rational or null.
+
+    // We cache the best known decimal result in mResultString.  Whenever that is
+    // non-null, it is computed to exactly mResultStringOffset, which is always > 0.
+    // The cache is filled in by the UI thread.
+    // Valid only if mResultString is non-null and !mChangedValue.
+    private String mResultString;
+    private int mResultStringOffset = 0;
+
+    // Number of digits to which (possibly incomplete) evaluation has been requested.
+    // Only accessed by UI thread.
+    private int mResultStringOffsetReq;  // Number of digits that have been
+
+    public static final int INVALID_MSD = Integer.MAX_VALUE;
+
+    // Position of most significant digit in current cached result, if determined.  This is just
+    // the index in mResultString holding the msd.
+    private int mMsdIndex = INVALID_MSD;
+
+    // Currently running expression evaluator, if any.
+    private AsyncEvaluator mEvaluator;
+
+    // The one and only un-cancelled and currently running reevaluator. Touched only by UI thread.
+    private AsyncReevaluator mCurrentReevaluator;
+
     Evaluator(Calculator calculator,
               CalculatorResult resultDisplay) {
         mCalculator = calculator;
@@ -209,86 +190,36 @@
         mDegreeMode = mSharedPrefs.getBoolean(KEY_PREF_DEGREE_MODE, false);
     }
 
-    // Result of asynchronous reevaluation
-    class ReevalResult {
-        ReevalResult(String s, int p) {
-            mNewCache = s;
-            mNewCacheDigs = p;
-        }
-        final String mNewCache;
-        final int mNewCacheDigs;
-    }
-
-    // Compute new cache contents accurate to prec digits to the right
-    // of the decimal point.  Ensure that redisplay() is called after
-    // doing so.  If the evaluation fails for reasons other than a
-    // timeout, ensure that DisplayError() is called.
-    class AsyncReevaluator extends AsyncTask<Integer, Void, ReevalResult> {
-        @Override
-        protected ReevalResult doInBackground(Integer... prec) {
-            try {
-                int eval_prec = prec[0].intValue();
-                return new ReevalResult(mVal.toString(eval_prec), eval_prec);
-            } catch(ArithmeticException e) {
-                return null;
-            } catch(CR.PrecisionOverflowException e) {
-                return null;
-            } catch(CR.AbortedException e) {
-                // Should only happen if the task was cancelled,
-                // in which case we don't look at the result.
-                return null;
-            }
-        }
-        @Override
-        protected void onPostExecute(ReevalResult result) {
-            if (result == null) {
-                // This should only be possible in the extremely rare
-                // case of encountering a domain error while reevaluating
-                // or in case of a precision overflow.  We don't know of
-                // a way to get the latter with a plausible amount of
-                // user input.
-                mCalculator.onError(R.string.error_nan);
-            } else {
-                if (result.mNewCacheDigs < mCacheDigs) {
-                    throw new AssertionError("Unexpected onPostExecute timing");
-                }
-                mCache = result.mNewCache;
-                mCacheDigs = result.mNewCacheDigs;
-                mCalculator.onReevaluate();
-            }
-            mCurrentReevaluator = null;
-        }
-        // On cancellation we do nothing; invoker should have
-        // left no trace of us.
-    }
-
-    // Result of initial asynchronous computation
+    /**
+     * Result of initial asynchronous result computation.
+     * Represents either an error or a result computed to an initial evaluation precision.
+     */
     private static class InitialResult {
-        InitialResult(CR val, BoundedRational ratVal, String s, int p, int idp) {
-            mErrorResourceId = Calculator.INVALID_RES_ID;
-            mVal = val;
-            mRatVal = ratVal;
-            mNewCache = s;
-            mNewCacheDigs = p;
-            mInitDisplayPrec = idp;
+        public final int errorResourceId;    // Error string or INVALID_RES_ID.
+        public final CR val;                 // Constructive real value.
+        public final BoundedRational ratVal; // Rational value or null.
+        public final String newResultString;       // Null iff it can't be computed.
+        public final int newResultStringOffset;
+        public final int initDisplayOffset;
+        InitialResult(CR v, BoundedRational rv, String s, int p, int idp) {
+            errorResourceId = Calculator.INVALID_RES_ID;
+            val = v;
+            ratVal = rv;
+            newResultString = s;
+            newResultStringOffset = p;
+            initDisplayOffset = idp;
         }
-        InitialResult(int errorResourceId) {
-            mErrorResourceId = errorResourceId;
-            mVal = CR.valueOf(0);
-            mRatVal = BoundedRational.ZERO;
-            mNewCache = "BAD";
-            mNewCacheDigs = 0;
-            mInitDisplayPrec = 0;
+        InitialResult(int errorId) {
+            errorResourceId = errorId;
+            val = CR.valueOf(0);
+            ratVal = BoundedRational.ZERO;
+            newResultString = "BAD";
+            newResultStringOffset = 0;
+            initDisplayOffset = 0;
         }
         boolean isError() {
-            return mErrorResourceId != Calculator.INVALID_RES_ID;
+            return errorResourceId != Calculator.INVALID_RES_ID;
         }
-        final int mErrorResourceId;
-        final CR mVal;
-        final BoundedRational mRatVal;
-        final String mNewCache;       // Null iff it can't be computed.
-        final int mNewCacheDigs;
-        final int mInitDisplayPrec;
     }
 
     private void displayCancelledMessage() {
@@ -302,21 +233,18 @@
             .show();
     }
 
+    // Maximum timeout for background computations.  Exceeding a few tens of seconds
+    // increases the risk of running out of memory and impacting the rest of the system.
     private final long MAX_TIMEOUT = 15000;
-                                   // Milliseconds.
-                                   // Longer is unlikely to help unless
-                                   // we get more heap space.
-    private long mTimeout = 2000;  // Timeout for requested evaluations,
-                                   // in milliseconds.
-                                   // This is currently not saved and restored
-                                   // with the state; we reset
-                                   // the timeout when the
-                                   // calculator is restarted.
-                                   // We'll call that a feature; others
-                                   // might argue it's a bug.
+
+    // Timeout for requested evaluations, in milliseconds.  This is currently not saved and
+    // restored with the state; we reset the timeout when the calculator is restarted.  We'll call
+    // that a feature; others might argue it's a bug.
+    private long mTimeout = 2000;
+
+    // Timeout for unrequested, speculative evaluations, in milliseconds.
     private final long QUICK_TIMEOUT = 1000;
-                                   // Timeout for unrequested, speculative
-                                   // evaluations, in milliseconds.
+
     private int mMaxResultBits = 120000;             // Don't try to display a larger result.
     private final int MAX_MAX_RESULT_BITS = 350000;  // Long timeout version.
     private final int QUICK_MAX_RESULT_BITS = 50000; // Instant result version.
@@ -342,12 +270,12 @@
     // disabled, until this computation completes.
     // Can result in an error display if something goes wrong.
     // By default we set a timeout to catch runaway computations.
-    class AsyncDisplayResult extends AsyncTask<Void, Void, InitialResult> {
+    class AsyncEvaluator extends AsyncTask<Void, Void, InitialResult> {
         private boolean mDm;  // degrees
         private boolean mRequired; // Result was requested by user.
         private boolean mQuiet;  // Suppress cancellation message.
         private Runnable mTimeoutRunnable = null;
-        AsyncDisplayResult(boolean dm, boolean required) {
+        AsyncEvaluator(boolean dm, boolean required) {
             mDm = dm;
             mRequired = required;
             mQuiet = !required;
@@ -379,12 +307,15 @@
             };
             mTimeoutHandler.postDelayed(mTimeoutRunnable, timeout);
         }
+        /**
+         * Is a computed result too big for decimal conversion?
+         */
         private boolean isTooBig(CalculatorExpr.EvalResult res) {
             int maxBits = mRequired ? mMaxResultBits : QUICK_MAX_RESULT_BITS;
-            if (res.mRatVal != null) {
-                return res.mRatVal.wholeNumberBits() > maxBits;
+            if (res.ratVal != null) {
+                return res.ratVal.wholeNumberBits() > maxBits;
             } else {
-                return res.mVal.get_appr(maxBits).bitLength() > 2;
+                return res.val.get_appr(maxBits).bitLength() > 2;
             }
         }
         @Override
@@ -395,35 +326,33 @@
                     // Avoid starting a long uninterruptible decimal conversion.
                     return new InitialResult(R.string.timeout);
                 }
-                int prec = INIT_PREC;
-                String initCache = res.mVal.toString(prec);
-                int msd = getMsdPos(initCache);
-                if (BoundedRational.asBigInteger(res.mRatVal) == null
+                int precOffset = INIT_PREC;
+                String initResult = res.val.toString(precOffset);
+                int msd = getMsdIndexOf(initResult);
+                if (BoundedRational.asBigInteger(res.ratVal) == null
                         && msd == INVALID_MSD) {
-                    prec = MAX_MSD_PREC;
-                    initCache = res.mVal.toString(prec);
-                    msd = getMsdPos(initCache);
+                    precOffset = MAX_MSD_PREC_OFFSET;
+                    initResult = res.val.toString(precOffset);
+                    msd = getMsdIndexOf(initResult);
                 }
-                int lsd = getLsd(res.mRatVal, initCache, initCache.indexOf('.'));
-                int initDisplayPrec = getPreferredPrec(initCache, msd, lsd);
-                int newPrec = initDisplayPrec + EXTRA_DIGITS;
-                if (newPrec > prec) {
-                    prec = newPrec;
-                    initCache = res.mVal.toString(prec);
+                final int lsdOffset = getLsdOffset(res.ratVal, initResult,
+                        initResult.indexOf('.'));
+                final int initDisplayOffset = getPreferredPrec(initResult, msd, lsdOffset);
+                final int newPrecOffset = initDisplayOffset + EXTRA_DIGITS;
+                if (newPrecOffset > precOffset) {
+                    precOffset = newPrecOffset;
+                    initResult = res.val.toString(precOffset);
                 }
-                return new InitialResult(res.mVal, res.mRatVal,
-                                         initCache, prec, initDisplayPrec);
+                return new InitialResult(res.val, res.ratVal,
+                        initResult, precOffset, initDisplayOffset);
             } catch (CalculatorExpr.SyntaxException e) {
                 return new InitialResult(R.string.error_syntax);
             } catch (BoundedRational.ZeroDivisionException e) {
-                // Division by zero caught by BoundedRational;
-                // the easy and more common case.
                 return new InitialResult(R.string.error_zero_divide);
             } catch(ArithmeticException e) {
                 return new InitialResult(R.string.error_nan);
             } catch(CR.PrecisionOverflowException e) {
-                // Extremely unlikely unless we're actually dividing by
-                // zero or the like.
+                // Extremely unlikely unless we're actually dividing by zero or the like.
                 return new InitialResult(R.string.error_overflow);
             } catch(CR.AbortedException e) {
                 return new InitialResult(R.string.error_aborted);
@@ -434,41 +363,41 @@
             mEvaluator = null;
             mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
             if (result.isError()) {
-                if (result.mErrorResourceId == R.string.timeout) {
+                if (result.errorResourceId == R.string.timeout) {
                     if (mRequired) {
                         displayTimeoutMessage();
                     }
                     mCalculator.onCancelled();
                 } else {
-                    mCalculator.onError(result.mErrorResourceId);
+                    mCalculator.onError(result.errorResourceId);
                 }
                 return;
             }
-            mVal = result.mVal;
-            mRatVal = result.mRatVal;
-            mCache = result.mNewCache;
-            mCacheDigs = result.mNewCacheDigs;
-            mLastDigs = result.mInitDisplayPrec;
-            int dotPos = mCache.indexOf('.');
-            String truncatedWholePart = mCache.substring(0, dotPos);
-            // Recheck display precision; it may change, since
-            // display dimensions may have been unknow the first time.
-            // In that case the initial evaluation precision should have
+            mVal = result.val;
+            mRatVal = result.ratVal;
+            // TODO: If the new result ends in lots of zeroes, and we have a rational result which
+            // is greater than (in absolute value) the result string, we should subtract 1 ulp
+            // from the result string.  That will prevent a later change from zeroes to nines.  We
+            // know that the correct, rounded-toward-zero result has nines.
+            mResultString = result.newResultString;
+            mResultStringOffset = result.newResultStringOffset;
+            final int dotIndex = mResultString.indexOf('.');
+            String truncatedWholePart = mResultString.substring(0, dotIndex);
+            // Recheck display precision; it may change, since display dimensions may have been
+            // unknow the first time.  In that case the initial evaluation precision should have
             // been conservative.
-            // TODO: Could optimize by remembering display size and
-            // checking for change.
-            int init_prec = result.mInitDisplayPrec;
-            int msd = getMsdPos(mCache);
-            int leastDigPos = getLsd(mRatVal, mCache, dotPos);
-            int new_init_prec = getPreferredPrec(mCache, msd, leastDigPos);
-            if (new_init_prec < init_prec) {
-                init_prec = new_init_prec;
+            // TODO: Could optimize by remembering display size and checking for change.
+            int initPrecOffset = result.initDisplayOffset;
+            final int msdIndex = getMsdIndexOf(mResultString);
+            final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+            final int newInitPrecOffset = getPreferredPrec(mResultString, msdIndex, leastDigOffset);
+            if (newInitPrecOffset < initPrecOffset) {
+                initPrecOffset = newInitPrecOffset;
             } else {
-                // They should be equal.  But nothing horrible should
-                // happen if they're not. e.g. because
-                // CalculatorResult.MAX_WIDTH was too small.
+                // They should be equal.  But nothing horrible should happen if they're not. e.g.
+                // because CalculatorResult.MAX_WIDTH was too small.
             }
-            mCalculator.onEvaluate(init_prec, msd, leastDigPos, truncatedWholePart);
+            mCalculator.onEvaluate(initPrecOffset, msdIndex, leastDigOffset, truncatedWholePart);
         }
         @Override
         protected void onCancelled(InitialResult result) {
@@ -476,47 +405,117 @@
             mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
             if (mRequired && !mQuiet) {
                 displayCancelledMessage();
-            } // Otherwise timeout processing displayed message.
+            } // Otherwise, if mRequired, timeout processing displayed message.
             mCalculator.onCancelled();
             // Just drop the evaluation; Leave expression displayed.
             return;
         }
     }
 
+    /**
+     * Result of asynchronous reevaluation.
+     */
+    private static class ReevalResult {
+        public final String newResultString;
+        public final int newResultStringOffset;
+        ReevalResult(String s, int p) {
+            newResultString = s;
+            newResultStringOffset = p;
+        }
+    }
 
-    // Start an evaluation to prec, and ensure that the
-    // display is redrawn when it completes.
-    private void ensureCachePrec(int prec) {
-        if (mCache != null && mCacheDigs >= prec
-                || mCacheDigsReq >= prec) return;
+    /**
+     * Compute new mResultString contents to prec digits to the right of the decimal point.
+     * Ensure that onReevaluate() is called after doing so.  If the evaluation fails for reasons
+     * other than a timeout, ensure that onError() is called.
+     */
+    private class AsyncReevaluator extends AsyncTask<Integer, Void, ReevalResult> {
+        @Override
+        protected ReevalResult doInBackground(Integer... prec) {
+            try {
+                final int precOffset = prec[0].intValue();
+                return new ReevalResult(mVal.toString(precOffset), precOffset);
+            } catch(ArithmeticException e) {
+                return null;
+            } catch(CR.PrecisionOverflowException e) {
+                return null;
+            } catch(CR.AbortedException e) {
+                // Should only happen if the task was cancelled, in which case we don't look at
+                // the result.
+                return null;
+            }
+        }
+        @Override
+        protected void onPostExecute(ReevalResult result) {
+            if (result == null) {
+                // This should only be possible in the extremely rare case of encountering a
+                // domain error while reevaluating or in case of a precision overflow.  We don't
+                // know of a way to get the latter with a plausible amount of user input.
+                mCalculator.onError(R.string.error_nan);
+            } else {
+                if (result.newResultStringOffset < mResultStringOffset) {
+                    throw new AssertionError("Unexpected onPostExecute timing");
+                }
+                // FIXME: We are assuming that the most significant digit never moves to the left,
+                // i.e. that 0.99999 doesn't ever change to 1.00000.  Informally that makes sense,
+                // in that we can only produce the former result after a computation showing that
+                // the true answer is < 1 (otherwise we would have violated our 1 ulp error
+                // bound), and higher precision evaluations should preserve that bound.  But I
+                // don't know how to prove that.  Indeed, it seems like this could be violated
+                // if one of the CR operations, before rounding, produced an error that was
+                // almost exactly at it's error bound of 1/2ulp.  (Since we calculate ahead
+                // so far, we really mean "almost exactly", which makes it very difficult to
+                // generate a test case.)
+                // Instead, we should just check whether (a) all added digits are zeroes, and (b)
+                // any trailing 9's have been replaced.  In that case, we just use the original
+                // result with 9's appended.  This must be correct, since our 1 ulp error bound
+                // implies that the correct answer is between the two.  This has the unfortunate
+                // consequence that we are introducing code that is extremely unlikely to ever be
+                // exercised, and thus very difficult to test.
+                mResultString = result.newResultString;
+                mResultStringOffset = result.newResultStringOffset;
+                mCalculator.onReevaluate();
+            }
+            mCurrentReevaluator = null;
+        }
+        // On cancellation we do nothing; invoker should have left no trace of us.
+    }
+
+    /**
+     * If necessary, start an evaluation to precOffset.
+     * Ensure that the display is redrawn when it completes.
+     */
+    private void ensureCachePrec(int precOffset) {
+        if (mResultString != null && mResultStringOffset >= precOffset
+                || mResultStringOffsetReq >= precOffset) return;
         if (mCurrentReevaluator != null) {
             // Ensure we only have one evaluation running at a time.
             mCurrentReevaluator.cancel(true);
             mCurrentReevaluator = null;
         }
         mCurrentReevaluator = new AsyncReevaluator();
-        mCacheDigsReq = prec + PRECOMPUTE_DIGITS;
-        if (mCache != null) {
-            mCacheDigsReq += mCacheDigsReq / PRECOMPUTE_DIVISOR;
+        mResultStringOffsetReq = precOffset + PRECOMPUTE_DIGITS;
+        if (mResultString != null) {
+            mResultStringOffsetReq += mResultStringOffsetReq / PRECOMPUTE_DIVISOR;
         }
-        mCurrentReevaluator.execute(mCacheDigsReq);
+        mCurrentReevaluator.execute(mResultStringOffsetReq);
     }
 
     /**
      * Return the rightmost nonzero digit position, if any.
      * @param ratVal Rational value of result or null.
      * @param cache Current cached decimal string representation of result.
-     * @param decPos Index of decimal point in cache.
+     * @param decIndex Index of decimal point in cache.
      * @result Position of rightmost nonzero digit relative to decimal point.
      *         Integer.MIN_VALUE if ratVal is zero.  Integer.MAX_VALUE if there is no lsd,
      *         or we cannot determine it.
      */
-    int getLsd(BoundedRational ratVal, String cache, int decPos) {
+    int getLsdOffset(BoundedRational ratVal, String cache, int decIndex) {
         if (ratVal != null && ratVal.signum() == 0) return Integer.MIN_VALUE;
         int result = BoundedRational.digitsRequired(ratVal);
         if (result == 0) {
             int i;
-            for (i = -1; decPos + i > 0 && cache.charAt(decPos + i) == '0'; --i) { }
+            for (i = -1; decIndex + i > 0 && cache.charAt(decIndex + i) == '0'; --i) { }
             result = i;
         }
         return result;
@@ -528,30 +527,33 @@
      * @param cache Current approximation as string.
      * @param msd Position of most significant digit in result.  Index in cache.
      *            Can be INVALID_MSD if we haven't found it yet.
-     * @param lastDigit Position of least significant digit (1 = tenths digit)
+     * @param lastDigitOffset Position of least significant digit (1 = tenths digit)
      *                  or Integer.MAX_VALUE.
      */
-    int getPreferredPrec(String cache, int msd, int lastDigit) {
-        int lineLength = mResult.getMaxChars();
-        int wholeSize = cache.indexOf('.');
-        int negative = cache.charAt(0) == '-' ? 1 : 0;
+    private int getPreferredPrec(String cache, int msd, int lastDigitOffset) {
+        final int lineLength = mResult.getMaxChars();
+        final int wholeSize = cache.indexOf('.');
+        final int negative = cache.charAt(0) == '-' ? 1 : 0;
         // Don't display decimal point if result is an integer.
-        if (lastDigit == 0) lastDigit = -1;
-        if (lastDigit != Integer.MAX_VALUE) {
-            if (wholeSize <= lineLength && lastDigit <= 0) {
+        if (lastDigitOffset == 0) {
+            lastDigitOffset = -1;
+        }
+        if (lastDigitOffset != Integer.MAX_VALUE) {
+            if (wholeSize <= lineLength && lastDigitOffset <= 0) {
                 // Exact integer.  Prefer to display as integer, without decimal point.
                 return -1;
             }
-            if (lastDigit >= 0 && wholeSize + lastDigit + 1 /* dec.pt. */ <= lineLength) {
+            if (lastDigitOffset >= 0
+                    && wholeSize + lastDigitOffset + 1 /* decimal pt. */ <= lineLength) {
                 // Display full exact number wo scientific notation.
-                return lastDigit;
+                return lastDigitOffset;
             }
         }
         if (msd > wholeSize && msd <= wholeSize + EXP_COST + 1) {
             // Display number without scientific notation.  Treat leading zero as msd.
             msd = wholeSize - 1;
         }
-        if (msd > wholeSize + MAX_MSD_PREC) {
+        if (msd > wholeSize + MAX_MSD_PREC_OFFSET) {
             // Display a probable but uncertain 0 as "0.000000000",
             // without exponent.  That's a judgment call, but less likely
             // to confuse naive users.  A more informative and confusing
@@ -576,10 +578,10 @@
      *              that if it doesn't contain enough significant digits, we can
      *              reasonably abbreviate as SHORT_UNCERTAIN_ZERO.
      * @param msdIndex Index of most significant digit in cache, or INVALID_MSD.
-     * @param lsd Position of least significant digit in finite representation,
+     * @param lsdOffset Position of least significant digit in finite representation,
      *            relative to decimal point, or MAX_VALUE.
      */
-    private String getShortString(String cache, int msdIndex, int lsd) {
+    private String getShortString(String cache, int msdIndex, int lsdOffset) {
         // This somewhat mirrors the display formatting code, but
         // - The constants are different, since we don't want to use the whole display.
         // - This is an easier problem, since we don't support scrolling and the length
@@ -594,7 +596,7 @@
             msdIndex = INVALID_MSD;
         }
         if (msdIndex == INVALID_MSD) {
-            if (lsd < INIT_PREC) {
+            if (lsdOffset < INIT_PREC) {
                 return "0";
             } else {
                 return SHORT_UNCERTAIN_ZERO;
@@ -602,19 +604,19 @@
         }
         // Avoid scientific notation for small numbers of zeros.
         // Instead stretch significant digits to include decimal point.
-        if (lsd < -1 && dotIndex - msdIndex + negative <= SHORT_TARGET_LENGTH
-            && lsd >= -CalculatorResult.MAX_TRAILING_ZEROES - 1) {
+        if (lsdOffset < -1 && dotIndex - msdIndex + negative <= SHORT_TARGET_LENGTH
+            && lsdOffset >= -CalculatorResult.MAX_TRAILING_ZEROES - 1) {
             // Whole number that fits in allotted space.
             // CalculatorResult would not use scientific notation either.
-            lsd = -1;
+            lsdOffset = -1;
         }
         if (msdIndex > dotIndex) {
             if (msdIndex <= dotIndex + EXP_COST + 1) {
                 // Preferred display format inthis cases is with leading zeroes, even if
                 // it doesn't fit entirely.  Replicate that here.
                 msdIndex = dotIndex - 1;
-            } else if (lsd <= SHORT_TARGET_LENGTH - negative - 2
-                    && lsd <= CalculatorResult.MAX_LEADING_ZEROES + 1) {
+            } else if (lsdOffset <= SHORT_TARGET_LENGTH - negative - 2
+                    && lsdOffset <= CalculatorResult.MAX_LEADING_ZEROES + 1) {
                 // Fraction that fits entirely in allotted space.
                 // CalculatorResult would not use scientific notation either.
                 msdIndex = dotIndex -1;
@@ -625,10 +627,10 @@
             // Adjust for the fact that the decimal point itself takes space.
             exponent--;
         }
-        if (lsd != Integer.MAX_VALUE) {
-            int lsdIndex = dotIndex + lsd;
-            int totalDigits = lsdIndex - msdIndex + negative + 1;
-            if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsd >= -1) {
+        if (lsdOffset != Integer.MAX_VALUE) {
+            final int lsdIndex = dotIndex + lsdOffset;
+            final int totalDigits = lsdIndex - msdIndex + negative + 1;
+            if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsdOffset >= -1) {
                 // Fits, no exponent needed.
                 return negativeSign + cache.substring(msdIndex, lsdIndex + 1);
             }
@@ -648,52 +650,63 @@
                 + KeyMaps.ELLIPSIS + "E" + exponent;
     }
 
-    // Return the most significant digit position in the given string
-    // or INVALID_MSD.
-    public static int getMsdPos(String s) {
-        int len = s.length();
-        int nonzeroPos = -1;
+    /**
+     * Return the most significant digit index in the given numeric string.
+     * Return INVALID_MSD if there are not enough digits to prove the numeric value is
+     * different from zero.  As usual, we assume an error of strictly less than 1 ulp.
+     */
+    public static int getMsdIndexOf(String s) {
+        final int len = s.length();
+        int nonzeroIndex = -1;
         for (int i = 0; i < len; ++i) {
             char c = s.charAt(i);
             if (c != '-' && c != '.' && c != '0') {
-                nonzeroPos = i;
+                nonzeroIndex = i;
                 break;
             }
         }
-        if (nonzeroPos >= 0 &&
-            (nonzeroPos < len - 1 || s.charAt(nonzeroPos) != '1')) {
-                return nonzeroPos;
+        if (nonzeroIndex >= 0 && (nonzeroIndex < len - 1 || s.charAt(nonzeroIndex) != '1')) {
+            return nonzeroIndex;
         } else {
-            // Unknown, or could change on reevaluation
             return INVALID_MSD;
         }
     }
 
-    // Return most significant digit position in the cache, if determined,
-    // INVALID_MSD ow.
-    // If unknown, and we've computed less than DESIRED_PREC,
-    // schedule reevaluation and redisplay, with higher precision.
-    int getMsd() {
-        if (mMsd != INVALID_MSD) return mMsd;
+    /**
+     * Return most significant digit index in the currently computed result.
+     * Returns an index in the result character array.  Return INVALID_MSD if the current result
+     * is too close to zero to determine the result.
+     */
+    private int getMsdIndex() {
+        // FIXME: We currently never adjust msd once computed, even if the result changes
+        // from 0.100000...  to 0.0999999...  (We know it can't change in the other direction.)
+        // It would be cheap to increment it if the current "most significant digit" is zero.
+        // And it would make it easier to reason about the code.  We should do that.
+        if (mMsdIndex != INVALID_MSD) return mMsdIndex;
         if (mRatVal != null && mRatVal.signum() == 0) {
             return INVALID_MSD;  // None exists
         }
-        int res = INVALID_MSD;
-        if (mCache != null) {
-            res = getMsdPos(mCache);
+        int result = INVALID_MSD;
+        if (mResultString != null) {
+            result = getMsdIndexOf(mResultString);
         }
-        if (res == INVALID_MSD && mEvaluator == null
-            && mCurrentReevaluator == null && mCacheDigs < MAX_MSD_PREC) {
-            // We assert that mCache is not null, since there is no
+        // FIXME: I think the following conditional is no longer needed.  The initial
+        // background evaluation already ensures that either the msd is know, or we've
+        // evaluated to MAX_MSD_PREC_OFFSET.
+        if (result == INVALID_MSD && mEvaluator == null
+            && mCurrentReevaluator == null && mResultStringOffset < MAX_MSD_PREC_OFFSET) {
+            // We assert that mResultString is not null, since there is no
             // evaluator running.
-            ensureCachePrec(MAX_MSD_PREC);
+            ensureCachePrec(MAX_MSD_PREC_OFFSET);
             // Could reevaluate more incrementally, but we suspect that if
             // we have to reevaluate at all, the result is probably zero.
         }
-        return res;
+        return result;
     }
 
-    // Return a string with n placeholder characters.
+    /**
+     * Return a string with n placeholder characters.
+     */
     private String getPadding(int n) {
         StringBuilder padding = new StringBuilder();
         for (int i = 0; i < n; ++i) {
@@ -702,108 +715,107 @@
         return padding.toString();
     }
 
-    // Return the number of zero characters at the beginning of s
+    /**
+     * Return the number of zero characters at the beginning of s.
+     */
     private int leadingZeroes(String s) {
-        int res = 0;
-        int len = s.length();
-        for (res = 0; res < len && s.charAt(res) == '0'; ++res) {}
-        return res;
+        int result = 0;
+        final int len = s.length();
+        for (result = 0; result < len && s.charAt(result) == '0'; ++result) {}
+        return result;
     }
 
-    private static final int MIN_DIGS = 5;
-            // Leave at least this many digits from the whole number
-            // part on the screen, to avoid silly displays like 1E1.
-    // Return result to exactly prec[0] digits to the right of the
-    // decimal point.
-    // The result should be no longer than maxDigs.
-    // No exponent or other indication of precision is added.
-    // The result is returned immediately, based on the
-    // current cache contents, but it may contain question
-    // marks for unknown digits.  It may also use uncertain
-    // digits within EXTRA_DIGITS.  If either of those occurred,
-    // schedule a reevaluation and redisplay operation.
-    // Uncertain digits never appear to the left of the decimal point.
-    // digs may be negative to only retrieve digits to the left
-    // of the decimal point.  (prec[0] = 0 means we include
-    // the decimal point, but nothing to the right.  prec[0] = -1
-    // means we drop the decimal point and start at the ones
-    // position.  Should not be invoked if mVal is null.
-    // This essentially just returns a substring of the full result;
-    // a leading minus sign or leading digits can be dropped.
-    // Result uses US conventions; is NOT internationalized.
-    // We set negative[0] if the number as a whole is negative,
-    // since we may drop the minus sign.
-    // We set truncated[0] if leading nonzero digits were dropped.
-    // getRational() can be used to determine whether the result
-    // is exact, or whether we dropped trailing digits.
-    // If the requested prec[0] value is out of range, we update
-    // it in place and use the updated value.  But we do not make it
-    // greater than maxPrec.
-    public String getString(int[] prec, int maxPrec, int maxDigs,
-                            boolean[] truncated, boolean[] negative) {
-        int digs = prec[0];
-        mLastDigs = digs;
+    // Refuse to scroll past the point at which this many digits from the whole number
+    // part of the result are still displayed.  Avoids sily displays like 1E1.
+    private static final int MIN_DISPLAYED_DIGS = 5;
+
+    /**
+     * Return result to precOffset[0] digits to the right of the decimal point.
+     * PrecOffset[0] is updated if the original value is out of range.  No exponent or other
+     * indication of precision is added.  The result is returned immediately, based on the current
+     * cache contents, but it may contain question marks for unknown digits.  It may also use
+     * uncertain digits within EXTRA_DIGITS.  If either of those occurred, schedule a reevaluation
+     * and redisplay operation.  Uncertain digits never appear to the left of the decimal point.
+     * PrecOffset[0] may be negative to only retrieve digits to the left of the decimal point.
+     * (precOffset[0] = 0 means we include the decimal point, but nothing to the right.
+     * precOffset[0] = -1 means we drop the decimal point and start at the ones position.  Should
+     * not be invoked before the onEvaluate() callback is received.  This essentially just returns
+     * a substring of the full result; a leading minus sign or leading digits can be dropped.
+     * Result uses US conventions; is NOT internationalized.  Use getRational() to determine
+     * whether the result is exact, or whether we dropped trailing digits.
+     *
+     * @param precOffset Zeroth element indicates desired and actual precision
+     * @param maxPrecOffset Maximum adjusted precOffset[0]
+     * @param maxDigs Maximum length of result 
+     * @param truncated Zeroth element is set if leading nonzero digits were dropped
+     * @param negative Zeroth element is set of the result is negative.
+     */
+    public String getString(int[] precOffset, int maxPrecOffset, int maxDigs, boolean[] truncated,
+            boolean[] negative) {
+        int currentPrecOffset = precOffset[0];
         // Make sure we eventually get a complete answer
-            if (mCache == null) {
-                ensureCachePrec(digs + EXTRA_DIGITS);
-                // Nothing else to do now; seems to happen on rare occasion
-                // with weird user input timing;
-                // Will repair itself in a jiffy.
-                return getPadding(1);
-            } else {
-                ensureCachePrec(digs + EXTRA_DIGITS
-                        + mCache.length() / EXTRA_DIVISOR);
+        if (mResultString == null) {
+            ensureCachePrec(currentPrecOffset + EXTRA_DIGITS);
+            // Nothing else to do now; seems to happen on rare occasion with weird user input
+            // timing; Will repair itself in a jiffy.
+            return getPadding(1);
+        } else {
+            ensureCachePrec(currentPrecOffset + EXTRA_DIGITS + mResultString.length()
+                    / EXTRA_DIVISOR);
+        }
+        // Compute an appropriate substring of mResultString.  Pad if necessary.
+        final int len = mResultString.length();
+        final boolean myNegative = mResultString.charAt(0) == '-';
+        negative[0] = myNegative;
+        // Don't scroll left past leftmost digits in mResultString unless that still leaves an
+        // integer.
+            int integralDigits = len - mResultStringOffset;
+                            // includes 1 for dec. pt
+            if (myNegative) {
+                --integralDigits;
             }
-        // Compute an appropriate substring of mCache.
-        // We avoid returning a huge string to minimize string
-        // allocation during scrolling.
-        // Pad as needed.
-            final int len = mCache.length();
-            final boolean myNegative = mCache.charAt(0) == '-';
-            negative[0] = myNegative;
-            // Don't scroll left past leftmost digits in mCache
-            // unless that still leaves an integer.
-                int integralDigits = len - mCacheDigs;
-                                // includes 1 for dec. pt
-                if (myNegative) --integralDigits;
-                int minDigs = Math.min(-integralDigits + MIN_DIGS, -1);
-                digs = Math.min(Math.max(digs, minDigs), maxPrec);
-                prec[0] = digs;
-            int offset = mCacheDigs - digs; // trailing digits to drop
-            int deficit = 0;  // The number of digits we're short
-            if (offset < 0) {
-                offset = 0;
-                deficit = Math.min(digs - mCacheDigs, maxDigs);
-            }
-            int endIndx = len - offset;
-            if (endIndx < 1) return " ";
-            int startIndx = (endIndx + deficit <= maxDigs) ?
-                                0
-                                : endIndx + deficit - maxDigs;
-            truncated[0] = (startIndx > getMsd());
-            String res = mCache.substring(startIndx, endIndx);
-            if (deficit > 0) {
-                res = res + getPadding(deficit);
-                // Since we always compute past the decimal point,
-                // this never fills in the spot where the decimal point
-                // should go, and the rest of this can treat the
-                // made-up symbols as though they were digits.
-            }
-            return res;
+            int minPrecOffset = Math.min(MIN_DISPLAYED_DIGS - integralDigits, -1);
+            currentPrecOffset = Math.min(Math.max(currentPrecOffset, minPrecOffset),
+                    maxPrecOffset);
+            precOffset[0] = currentPrecOffset;
+        int extraDigs = mResultStringOffset - currentPrecOffset; // trailing digits to drop
+        int deficit = 0;  // The number of digits we're short
+        if (extraDigs < 0) {
+            extraDigs = 0;
+            deficit = Math.min(currentPrecOffset - mResultStringOffset, maxDigs);
+        }
+        int endIndex = len - extraDigs;
+        if (endIndex < 1) {
+            return " ";
+        }
+        int startIndex = Math.max(endIndex + deficit - maxDigs, 0);
+        truncated[0] = (startIndex > getMsdIndex());
+        String result = mResultString.substring(startIndex, endIndex);
+        if (deficit > 0) {
+            result += getPadding(deficit);
+            // Since we always compute past the decimal point, this never fills in the spot
+            // where the decimal point should go, and we can otherwise treat placeholders
+            // as though they were digits.
+        }
+        return result;
     }
 
-    // Return rational representation of current result, if any.
+    /**
+     * Return rational representation of current result, if any.
+     * Return null if the result is irrational, or we couldn't track the rational value,
+     * e.g. because the denominator got too big.
+     */
     public BoundedRational getRational() {
         return mRatVal;
     }
 
     private void clearCache() {
-        mCache = null;
-        mCacheDigs = mCacheDigsReq = 0;
-        mMsd = INVALID_MSD;
+        mResultString = null;
+        mResultStringOffset = mResultStringOffsetReq = 0;
+        mMsdIndex = INVALID_MSD;
     }
 
-    void clear() {
+    public void clear() {
         mExpr.clear();
         clearCache();
     }
@@ -813,18 +825,18 @@
      * Will result in display on completion.
      * @param required result was explicitly requested by user.
      */
-    private void reevaluateResult(boolean required) {
+    private void evaluateResult(boolean required) {
         clearCache();
-        mEvaluator = new AsyncDisplayResult(mDegreeMode, required);
+        mEvaluator = new AsyncEvaluator(mDegreeMode, required);
         mEvaluator.execute();
         mChangedValue = false;
     }
 
-    // Begin evaluation of result and display when ready.
-    // We assume this is called after each insertion and deletion.
-    // Thus if we are called twice with the same effective end of
-    // the formula, the evaluation is redundant.
-    void evaluateAndShowResult() {
+    /**
+     * Start optional evaluation of result and display when ready.
+     * Can quietly time out without a user-visible display.
+     */
+    public void evaluateAndShowResult() {
         if (!mChangedValue) {
             // Already done or in progress.
             return;
@@ -832,25 +844,27 @@
         // In very odd cases, there can be significant latency to evaluate.
         // Don't show obsolete result.
         mResult.clear();
-        reevaluateResult(false);
+        evaluateResult(false);
     }
 
-    // Ensure that we either display a result or complain.
-    // Does not invalidate a previously computed cache.
-    // We presume that any prior result was computed using the same
-    // expression.
-    void requireResult() {
-        if (mCache == null || mChangedValue) {
+    /**
+     * Start required evaluation of result and display when ready.
+     * Will eventually call back mCalculator to display result or error, or display
+     * a timeout message.  Uses longer timeouts than optional evaluation.
+     */ 
+    public void requireResult() {
+        if (mResultString == null || mChangedValue) {
             // Restart evaluator in requested mode, i.e. with longer timeout.
             cancelAll(true);
-            reevaluateResult(true);
+            evaluateResult(true);
         } else {
             // Notify immediately, reusing existing result.
-            int dotPos = mCache.indexOf('.');
-            String truncatedWholePart = mCache.substring(0, dotPos);
-            int leastDigOffset = getLsd(mRatVal, mCache, dotPos);
-            int msdIndex = getMsd();
-            int preferredPrecOffset = getPreferredPrec(mCache, msdIndex, leastDigOffset);
+            final int dotIndex = mResultString.indexOf('.');
+            final String truncatedWholePart = mResultString.substring(0, dotIndex);
+            final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+            final int msdIndex = getMsdIndex();
+            final int preferredPrecOffset = getPreferredPrec(mResultString, msdIndex,
+                    leastDigOffset);
             mCalculator.onEvaluate(preferredPrecOffset, msdIndex, leastDigOffset,
                     truncatedWholePart);
         }
@@ -861,10 +875,10 @@
      * @param quiet suppress cancellation message
      * @return      true if we cancelled an initial evaluation
      */
-    boolean cancelAll(boolean quiet) {
+    public boolean cancelAll(boolean quiet) {
         if (mCurrentReevaluator != null) {
             mCurrentReevaluator.cancel(true);
-            mCacheDigsReq = mCacheDigs;
+            mResultStringOffsetReq = mResultStringOffset;
             // Backgound computation touches only constructive reals.
             // OK not to wait.
             mCurrentReevaluator = null;
@@ -888,10 +902,14 @@
         return false;
     }
 
-    void restoreInstanceState(DataInput in) {
+    /**
+     * Restore the evaluator state, including the expression and any saved value.
+     */
+    public void restoreInstanceState(DataInput in) {
         mChangedValue = true;
         try {
             CalculatorExpr.initExprInput();
+            // FIXME: Do we still need to restore DegreeMode here?
             mDegreeMode = in.readBoolean();
             mExpr = new CalculatorExpr(in);
             mSavedName = in.readUTF();
@@ -901,7 +919,10 @@
         }
     }
 
-    void saveInstanceState(DataOutput out) {
+    /**
+     * Save the evaluator state, including the expression and any saved value.
+     */
+    public void saveInstanceState(DataOutput out) {
         try {
             CalculatorExpr.initExprOutput();
             out.writeBoolean(mDegreeMode);
@@ -913,11 +934,14 @@
         }
     }
 
-    // Append a button press to the current expression.
-    // Return false if we rejected the insertion due to obvious
-    // syntax issues, and the expression is unchanged.
-    // Return true otherwise.
-    boolean append(int id) {
+
+    /**
+     * Append a button press to the current expression.
+     * @param id Button identifier for the character or operator to be added.
+     * @return false if we rejected the insertion due to obvious syntax issues, and the expression
+     * is unchanged; true otherwise
+     */
+    public boolean append(int id) {
         if (id == R.id.fun_10pow) {
             add10pow();  // Handled as macro expansion.
             return true;
@@ -927,7 +951,7 @@
         }
     }
 
-    void delete() {
+    public void delete() {
         mChangedValue = true;
         mExpr.delete();
     }
@@ -946,79 +970,80 @@
     }
 
     /**
-     * @return the {@link CalculatorExpr} representation of the current result
+     * @return the {@link CalculatorExpr} representation of the current result.
      */
-    CalculatorExpr getResultExpr() {
-        final int dotPos = mCache.indexOf('.');
-        final int leastDigPos = getLsd(mRatVal, mCache, dotPos);
+    private CalculatorExpr getResultExpr() {
+        final int dotIndex = mResultString.indexOf('.');
+        final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
         return mExpr.abbreviate(mVal, mRatVal, mDegreeMode,
-               getShortString(mCache, getMsdPos(mCache), leastDigPos));
+                getShortString(mResultString, getMsdIndexOf(mResultString), leastDigOffset));
     }
 
-    // Abbreviate the current expression to a pre-evaluated
-    // expression node, which will display as a short number.
-    // This should not be called unless the expression was
-    // previously evaluated and produced a non-error result.
-    // Pre-evaluated expressions can never represent an
-    // expression for which evaluation to a constructive real
-    // diverges.  Subsequent re-evaluation will also not diverge,
-    // though it may generate errors of various kinds.
-    // E.g. sqrt(-10^-1000)
-    void collapse() {
+    /**
+     * Abbreviate the current expression to a pre-evaluated expression node.
+     * This should not be called unless the expression was previously evaluated and produced a
+     * non-error result.  Pre-evaluated expressions can never represent an expression for which
+     * evaluation to a constructive real diverges.  Subsequent re-evaluation will also not
+     * diverge, though it may generate errors of various kinds.  E.g.  sqrt(-10^-1000) .
+     */
+    public void collapse() {
         final CalculatorExpr abbrvExpr = getResultExpr();
         clear();
         mExpr.append(abbrvExpr);
         mChangedValue = true;
     }
 
-    // Same as above, but put result in mSaved, leaving mExpr alone.
-    // Return false if result is unavailable.
-    boolean collapseToSaved() {
-        if (mCache == null) {
+    /**
+     * Abbreviate current expression, and put result in mSaved.
+     * mExpr is left alone.  Return false if result is unavailable.
+     */
+    public boolean collapseToSaved() {
+        if (mResultString == null) {
             return false;
         }
-
         final CalculatorExpr abbrvExpr = getResultExpr();
         mSaved.clear();
         mSaved.append(abbrvExpr);
         return true;
     }
 
-    Uri uriForSaved() {
+    private Uri uriForSaved() {
         return new Uri.Builder().scheme("tag")
                                 .encodedOpaquePart(mSavedName)
                                 .build();
     }
 
-    // Collapse the current expression to mSaved and return a URI
-    // describing this particular result, so that we can refer to it
-    // later.
-    Uri capture() {
+    /**
+     * Collapse the current expression to mSaved and return a URI describing it.
+     * describing this particular result, so that we can refer to it
+     * later.
+     */
+    public Uri capture() {
         if (!collapseToSaved()) return null;
         // Generate a new (entirely private) URI for this result.
         // Attempt to conform to RFC4151, though it's unclear it matters.
-        Date date = new Date();
-        TimeZone tz = TimeZone.getDefault();
+        final TimeZone tz = TimeZone.getDefault();
         DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
         df.setTimeZone(tz);
-        String isoDate = df.format(new Date());
+        final String isoDate = df.format(new Date());
         mSavedName = "calculator2.android.com," + isoDate + ":"
-                     + (new Random().nextInt() & 0x3fffffff);
-        Uri tag = uriForSaved();
-        return tag;
+                + (new Random().nextInt() & 0x3fffffff);
+        return uriForSaved();
     }
 
-    boolean isLastSaved(Uri uri) {
+    public boolean isLastSaved(Uri uri) {
         return uri.equals(uriForSaved());
     }
 
-    void addSaved() {
+    public void appendSaved() {
         mChangedValue = true;
         mExpr.append(mSaved);
     }
 
-    // Add the power of 10 operator to the expression.  This is treated
-    // essentially as a macro expansion.
+    /**
+     * Add the power of 10 operator to the expression.
+     * This is treated essentially as a macro expansion.
+     */
     private void add10pow() {
         CalculatorExpr ten = new CalculatorExpr();
         ten.add(R.id.digit_1);
@@ -1028,24 +1053,31 @@
         mExpr.add(R.id.op_pow);
     }
 
-    // Retrieve the main expression being edited.
-    // It is the callee's reponsibility to call cancelAll to cancel
-    // ongoing concurrent computations before modifying the result.
-    // TODO: Perhaps add functionality so we can keep this private?
-    CalculatorExpr getExpr() {
+    /**
+     * Retrieve the main expression being edited.
+     * It is the callee's reponsibility to call cancelAll to cancel ongoing concurrent
+     * computations before modifying the result.  The resulting expression should only
+     * be modified by the caller if either the expression value doesn't change, or in
+     * combination with another add() or delete() call that makes the value change apparent
+     * to us.
+     * TODO: Perhaps add functionality so we can keep this private?
+     */
+    public CalculatorExpr getExpr() {
         return mExpr;
     }
 
+    /**
+     * Maximum number of characters in a scientific notation exponent.
+     */
     private static final int MAX_EXP_CHARS = 8;
 
     /**
      * Return the index of the character after the exponent starting at s[offset].
      * Return offset if there is no exponent at that position.
-     * Exponents have syntax E[-]digit* .
-     * "E2" and "E-2" are valid.  "E+2" and "e2" are not.
+     * Exponents have syntax E[-]digit* .  "E2" and "E-2" are valid.  "E+2" and "e2" are not.
      * We allow any Unicode digits, and either of the commonly used minus characters.
      */
-    static int exponentEnd(String s, int offset) {
+    public static int exponentEnd(String s, int offset) {
         int i = offset;
         int len = s.length();
         if (i >= len - 1 || s.charAt(i) != 'E') {
@@ -1068,10 +1100,10 @@
     /**
      * Add the exponent represented by s[begin..end) to the constant at the end of current
      * expression.
-     * The end of the current expression must be a constant.
-     * Exponents have the same syntax as for exponentEnd().
+     * The end of the current expression must be a constant.  Exponents have the same syntax as
+     * for exponentEnd().
      */
-    void addExponent(String s, int begin, int end) {
+    public void addExponent(String s, int begin, int end) {
         int sign = 1;
         int exp = 0;
         int i = begin + 1;