diff options
author | Hans Boehm <hboehm@google.com> | 2015-07-09 23:01:56 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2015-07-09 23:01:56 +0000 |
commit | e1343214088af4742438c92b0d96c4ba8bbf0e2d (patch) | |
tree | 2da686b8ece2644cd8a7b0aa9de6364a8c8889d8 | |
parent | 36fe131b66fb2f6ad1efef582f717657d62b3eb1 (diff) | |
parent | c22691223c65a112de898d5e6406a5e30cb5639a (diff) | |
download | android_packages_apps_ExactCalculator-e1343214088af4742438c92b0d96c4ba8bbf0e2d.tar.gz android_packages_apps_ExactCalculator-e1343214088af4742438c92b0d96c4ba8bbf0e2d.tar.bz2 android_packages_apps_ExactCalculator-e1343214088af4742438c92b0d96c4ba8bbf0e2d.zip |
am c2269122: am 8f4a8c73: am 0b9806f6: Support pasting of scientific notation numbers
* commit 'c22691223c65a112de898d5e6406a5e30cb5639a':
Support pasting of scientific notation numbers
-rw-r--r-- | src/com/android/calculator2/Calculator.java | 26 | ||||
-rw-r--r-- | src/com/android/calculator2/CalculatorExpr.java | 85 | ||||
-rw-r--r-- | src/com/android/calculator2/CalculatorResult.java | 6 | ||||
-rw-r--r-- | src/com/android/calculator2/Evaluator.java | 55 | ||||
-rw-r--r-- | src/com/android/calculator2/KeyMaps.java | 7 |
5 files changed, 150 insertions, 29 deletions
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java index 5cb6685..f1c9835 100644 --- a/src/com/android/calculator2/Calculator.java +++ b/src/com/android/calculator2/Calculator.java @@ -903,7 +903,7 @@ public class Calculator extends Activity * are added to mUnprocessedChars, which is presumed to immediately precede the newly * added characters. * @param moreChars Characters to be added. - * @param explicit These characters were explicitly typed by the user. + * @param explicit These characters were explicitly typed by the user, not pasted. */ private void addChars(String moreChars, boolean explicit) { if (mUnprocessedChars != null) { @@ -911,9 +911,33 @@ public class Calculator extends Activity } int current = 0; int len = moreChars.length(); + boolean lastWasDigit = false; while (current < len) { char c = moreChars.charAt(current); int k = KeyMaps.keyForChar(c); + if (!explicit) { + int expEnd; + if (lastWasDigit && current != + (expEnd = Evaluator.exponentEnd(moreChars, current))) { + // Process scientific notation with 'E' when pasting, in spite of ambiguity + // with base of natural log. + // Otherwise the 10^x key is the user's friend. + mEvaluator.addExponent(moreChars, current, expEnd); + current = expEnd; + lastWasDigit = false; + continue; + } else { + boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT; + if (current == 0 && (isDigit || k == R.id.dec_point) + && mEvaluator.getExpr().hasTrailingConstant()) { + // Refuse to concatenate pasted content to trailing constant. + // This makes pasting of calculator results more consistent, whether or + // not the old calculator instance is still around. + addKeyToExpr(R.id.op_mul); + } + lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point); + } + } if (k != View.NO_ID) { mCurrentButton = findViewById(k); if (explicit) { diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java index 2162bb2..3023b5c 100644 --- a/src/com/android/calculator2/CalculatorExpr.java +++ b/src/com/android/calculator2/CalculatorExpr.java @@ -79,19 +79,22 @@ class CalculatorExpr { // Supports addition and removal of trailing characters; hence mutable. private static class Constant extends Token implements Cloneable { private boolean mSawDecimal; - String mWhole; // part before decimal point - private String mFraction; // part after decimal point + String mWhole; // String preceding decimal point. + private String mFraction; // String after decimal point. + private int mExponent; // Explicit exponent, only generated through addExponent. Constant() { mWhole = ""; mFraction = ""; - mSawDecimal = false; + mSawDecimal = false; + mExponent = 0; }; Constant(DataInput in) throws IOException { mWhole = in.readUTF(); mSawDecimal = in.readBoolean(); mFraction = in.readUTF(); + mExponent = in.readInt(); } @Override @@ -100,19 +103,33 @@ class CalculatorExpr { out.writeUTF(mWhole); out.writeBoolean(mSawDecimal); out.writeUTF(mFraction); + out.writeInt(mExponent); } // Given a button press, append corresponding digit. // We assume id is a digit or decimal point. // 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) { if (id == R.id.dec_point) { - if (mSawDecimal) return false; + if (mSawDecimal || mExponent != 0) return false; mSawDecimal = true; return true; } int val = KeyMaps.digVal(id); + if (mExponent != 0) { + if (Math.abs(mExponent) <= 10000) { + if (mExponent > 0) { + mExponent = 10 * mExponent + val; + } else { + mExponent = 10 * mExponent - val; + } + return true; + } else { // Too large; refuse + return false; + } + } if (mSawDecimal) { mFraction += val; } else { @@ -121,10 +138,18 @@ class CalculatorExpr { return true; } + 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() { - if (!mFraction.isEmpty()) { + if (mExponent != 0) { + mExponent /= 10; + // Once zero, it can only be added back with addExponent. + } else if (!mFraction.isEmpty()) { mFraction = mFraction.substring(0, mFraction.length() - 1); } else if (mSawDecimal) { mSawDecimal = false; @@ -146,28 +171,24 @@ class CalculatorExpr { result += '.'; result += mFraction; } - return KeyMaps.translateResult(result); - } - - // Eliminates leading decimal, which some of our - // other packages don't like. - // Meant for machine consumption: - // Doesn't internationalize decimal point or digits. - public String toEasyString() { - String result = mWhole; - if (result.isEmpty()) result = "0"; - if (mSawDecimal) { - result += '.'; - result += mFraction; + if (mExponent != 0) { + result += "E" + mExponent; } - return result; + return KeyMaps.translateResult(result); } + // Return non-null BoundedRational representation. public BoundedRational toRational() { String whole = mWhole; if (whole.isEmpty()) whole = "0"; BigInteger num = new BigInteger(whole + mFraction); BigInteger den = BigInteger.TEN.pow(mFraction.length()); + if (mExponent > 0) { + num = num.multiply(BigInteger.TEN.pow(mExponent)); + } + if (mExponent < 0) { + den = den.multiply(BigInteger.TEN.pow(-mExponent)); + } return new BoundedRational(num, den); } @@ -186,6 +207,7 @@ class CalculatorExpr { res.mWhole = mWhole; res.mFraction = mFraction; res.mSawDecimal = mSawDecimal; + res.mExponent = mExponent; return res; } } @@ -350,6 +372,15 @@ class CalculatorExpr { } } + boolean hasTrailingConstant() { + int s = mExpr.size(); + if (s == 0) { + return false; + } + Token t = mExpr.get(s-1); + return t instanceof Constant; + } + private boolean hasTrailingBinary() { int s = mExpr.size(); if (s == 0) return false; @@ -410,6 +441,15 @@ class CalculatorExpr { } /** + * Add exponent to the constant at the end of the expression. + * Assumes there is a constant at the end of the expression. + */ + void addExponent(int exp) { + Token lastTok = mExpr.get(mExpr.size() - 1); + ((Constant) lastTok).addExponent(exp); + } + + /** * Remove trailing op_add and op_sub operators. */ void removeTrailingAdditiveOperators() { @@ -595,18 +635,19 @@ class CalculatorExpr { // that was not used as part of the evaluation. private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException { Token t = mExpr.get(i); + BoundedRational ratVal; CR value; if (t instanceof Constant) { Constant c = (Constant)t; - value = CR.valueOf(c.toEasyString(),10); - return new EvalRet(i+1, value, c.toRational()); + ratVal = c.toRational(); + value = ratVal.CRValue(); + return new EvalRet(i+1, value, ratVal); } if (t instanceof PreEval) { PreEval p = (PreEval)t; return new EvalRet(i+1, p.mValue, p.mRatValue); } EvalRet argVal; - BoundedRational ratVal; switch(((Operator)(t)).mId) { case R.id.const_pi: return new EvalRet(i+1, CR.PI, null); diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java index 27f5d09..8d3dfee 100644 --- a/src/com/android/calculator2/CalculatorResult.java +++ b/src/com/android/calculator2/CalculatorResult.java @@ -349,7 +349,7 @@ public class CalculatorResult extends AlignedTextView { // We add ellipses and exponents in a way that leaves most digits in the position they // would have been in had we not done so. // This minimizes jumps as a result of scrolling. Result is NOT internationalized, - // uses "e" for exponent. + // uses "E" for exponent. public String formatResult(String in, int precOffset, int maxDigs, boolean truncated, boolean negative, int lastDisplayedOffset[], boolean forcePrecision) { final int minusSpace = negative ? 1 : 0; @@ -415,7 +415,7 @@ public class CalculatorResult extends AlignedTextView { result = result.substring(0, result.length() - dropDigits); lastDisplayedOffset[0] -= dropDigits; } - result = result + "e" + Integer.toString(exponent); + result = result + "E" + Integer.toString(exponent); } // else don't add zero exponent } if (truncated || negative && result.charAt(0) != '-') { @@ -504,7 +504,7 @@ public class CalculatorResult extends AlignedTextView { int maxChars = getMaxChars(); int lastDisplayedOffset[] = new int[1]; String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset, false); - int expIndex = result.indexOf('e'); + int expIndex = result.indexOf('E'); result = KeyMaps.translateResult(result); if (expIndex > 0 && result.indexOf('.') == -1) { // Gray out exponent if used as position indicator diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java index 9c4346e..4bdc56f 100644 --- a/src/com/android/calculator2/Evaluator.java +++ b/src/com/android/calculator2/Evaluator.java @@ -613,7 +613,7 @@ class Evaluator { } if (totalDigits <= SHORT_TARGET_LENGTH - 3) { return negativeSign + cache.charAt(msdIndex) + "." - + cache.substring(msdIndex + 1, lsdIndex + 1) + "e" + exponent; + + cache.substring(msdIndex + 1, lsdIndex + 1) + "E" + exponent; } } // We need to abbreviate. @@ -624,7 +624,7 @@ class Evaluator { // Need abbreviation + exponent return negativeSign + cache.charAt(msdIndex) + "." + cache.substring(msdIndex + 1, msdIndex + SHORT_TARGET_LENGTH - negative - 4) - + KeyMaps.ELLIPSIS + "e" + exponent; + + KeyMaps.ELLIPSIS + "E" + exponent; } // Return the most significant digit position in the given string @@ -1008,4 +1008,55 @@ class Evaluator { return mExpr; } + 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. + * We allow any Unicode digits, and either of the commonly used minus characters. + */ + static int exponentEnd(String s, int offset) { + int i = offset; + int len = s.length(); + if (i >= len - 1 || s.charAt(i) != 'E') { + return offset; + } + ++i; + if (KeyMaps.keyForChar(s.charAt(i)) == R.id.op_sub) { + ++i; + } + if (i == len || i > offset + MAX_EXP_CHARS || !Character.isDigit(s.charAt(i))) { + return offset; + } + ++i; + while (i < len && Character.isDigit(s.charAt(i))) { + ++i; + } + return i; + } + + /** + * 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(). + */ + void addExponent(String s, int begin, int end) { + int sign = 1; + int exp = 0; + int i = begin + 1; + // We do the decimal conversion ourselves to exactly match exponentEnd() conventions + // and handle various kinds of digits on input. Also avoids allocation. + if (KeyMaps.keyForChar(s.charAt(i)) == R.id.op_sub) { + sign = -1; + ++i; + } + for (; i < end; ++i) { + exp = 10 * exp + Character.digit(s.charAt(i), 10); + } + mExpr.addExponent(sign * exp); + mChangedValue = true; + } } diff --git a/src/com/android/calculator2/KeyMaps.java b/src/com/android/calculator2/KeyMaps.java index c28e80d..b4bfdf9 100644 --- a/src/com/android/calculator2/KeyMaps.java +++ b/src/com/android/calculator2/KeyMaps.java @@ -184,6 +184,8 @@ public class KeyMaps { public static final String ELLIPSIS = "\u2026"; + public static final char MINUS_SIGN = '\u2212'; + /** * Map key id to digit or NOT_DIGIT * Pure function. @@ -309,12 +311,15 @@ public class KeyMaps { case ',': return R.id.dec_point; case '-': + case MINUS_SIGN: return R.id.op_sub; case '+': return R.id.op_add; case '*': + case '\u00D7': // MULTIPLICATION SIGN return R.id.op_mul; case '/': + case '\u00F7': // DIVISION SIGN return R.id.op_div; // We no longer localize function names, so they can't start with an 'e' or 'p'. case 'e': @@ -337,7 +342,7 @@ public class KeyMaps { if (c == mDecimalPt) return R.id.dec_point; if (c == mPiChar) return R.id.const_pi; // pi is not translated, but it might be typable on a Greek keyboard, - // so we check ... + // or pasted in, so we check ... return View.NO_ID; } } |