summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/calculator2/Calculator.java26
-rw-r--r--src/com/android/calculator2/CalculatorExpr.java95
-rw-r--r--src/com/android/calculator2/CalculatorResult.java70
-rw-r--r--tests/README.txt2
4 files changed, 138 insertions, 55 deletions
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index fde9d38..84f92c8 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -448,19 +448,27 @@ public class Calculator extends Activity
}
}
+ /**
+ * Switch to INPUT from RESULT state in response to input of the specified button_id.
+ * View.NO_ID is treated as an incomplete function id.
+ */
+ private void switchToInput(int button_id) {
+ if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) {
+ mEvaluator.collapse();
+ } else {
+ announceClearedForAccessibility();
+ mEvaluator.clear();
+ }
+ setState(CalculatorState.INPUT);
+ }
+
// Add the given button id to input expression.
// If appropriate, clear the expression before doing so.
private void addKeyToExpr(int id) {
if (mCurrentState == CalculatorState.ERROR) {
setState(CalculatorState.INPUT);
} else if (mCurrentState == CalculatorState.RESULT) {
- if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
- mEvaluator.collapse();
- } else {
- announceClearedForAccessibility();
- mEvaluator.clear();
- }
- setState(CalculatorState.INPUT);
+ switchToInput(id);
}
if (!mEvaluator.append(id)) {
// TODO: Some user visible feedback?
@@ -927,6 +935,10 @@ public class Calculator extends Activity
int current = 0;
int len = moreChars.length();
boolean lastWasDigit = false;
+ if (mCurrentState == CalculatorState.RESULT && len != 0) {
+ // Clear display immediately for incomplete function name.
+ switchToInput(KeyMaps.keyForChar(moreChars.charAt(current)));
+ }
while (current < len) {
char c = moreChars.charAt(current);
int k = KeyMaps.keyForChar(c);
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index b387e8b..14d9236 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -217,12 +217,19 @@ class CalculatorExpr {
}
/**
- * Return BoundedRational representation of constant.
- * Never null.
+ * Return BoundedRational representation of constant, if well-formed.
+ * Result is never null.
*/
- public BoundedRational toRational() {
+ public BoundedRational toRational() throws SyntaxException {
String whole = mWhole;
- if (whole.isEmpty()) whole = "0";
+ if (whole.isEmpty()) {
+ if (mFraction.isEmpty()) {
+ // Decimal point without digits.
+ throw new SyntaxException();
+ } else {
+ whole = "0";
+ }
+ }
BigInteger num = new BigInteger(whole + mFraction);
BigInteger den = BigInteger.TEN.pow(mFraction.length());
if (mExponent > 0) {
@@ -310,7 +317,7 @@ class CalculatorExpr {
}
// In writing out PreEvals, we are careful to avoid writing
// out duplicates. We assume that two expressions are
- // duplicates if they have the same mVal. This avoids a
+ // duplicates if they have the same CR value. This avoids a
// potential exponential blow up in certain off cases and
// redundant evaluation after reading them back in.
// The parameter hash map maps expressions we've seen
@@ -1022,6 +1029,58 @@ class CalculatorExpr {
return new EvalRet(cpos, crVal, ratVal);
}
+ /**
+ * Is the subexpression starting at pos a simple percent constant?
+ * This is used to recognize exppressions like 200+10%, which we handle specially.
+ * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
+ * by either nothing or an additive operator.
+ * Note that we are intentionally far more restrictive in recognizing such expressions than
+ * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
+ * When in doubt, we fall back to the the naive interpretation of % as 1/100.
+ * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
+ * but is consistent with Google web search.
+ */
+ private boolean isPercent(int pos) {
+ if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
+ return false;
+ }
+ Token number = mExpr.get(pos);
+ if (number instanceof Operator) {
+ return false;
+ }
+ if (mExpr.size() == pos + 2) {
+ return true;
+ }
+ if (!(mExpr.get(pos + 2) instanceof Operator)) {
+ return false;
+ }
+ Operator op = (Operator) mExpr.get(pos + 2);
+ return op.id == R.id.op_add || op.id == R.id.op_sub;
+ }
+
+ /**
+ * Compute the multiplicative factor corresponding to an N% addition or subtraction.
+ * @param pos position of Constant or PreEval expression token corresponding to N
+ * @param isSubtraction this is a subtraction, as opposed to addition
+ * @param ec usable evaluation contex; only length matters
+ * @return Rational and CR values; position is pos + 2, i.e. after percent sign
+ */
+ private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
+ throws SyntaxException {
+ EvalRet tmp = evalUnary(pos, ec);
+ BoundedRational ratVal = isSubtraction ? BoundedRational.negate(tmp.ratVal)
+ : tmp.ratVal;
+ CR crVal = isSubtraction ? tmp.val.negate() : tmp.val;
+ ratVal = BoundedRational.add(BoundedRational.ONE,
+ BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH));
+ if (ratVal == null) {
+ crVal = CR.ONE.add(crVal.multiply(REAL_ONE_HUNDREDTH));
+ } else {
+ crVal = ratVal.CRValue();
+ }
+ return new EvalRet(pos + 2 /* after percent sign */, crVal, ratVal);
+ }
+
private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
EvalRet tmp = evalTerm(i, ec);
boolean is_plus;
@@ -1030,20 +1089,30 @@ class CalculatorExpr {
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.ratVal);
+ if (isPercent(cpos + 1)) {
+ tmp = getPercentFactor(cpos + 1, !is_plus, ec);
+ ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
if (ratVal == null) {
- crVal = crVal.add(tmp.val);
+ crVal = crVal.multiply(tmp.val);
} else {
crVal = ratVal.CRValue();
}
} else {
- ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
- if (ratVal == null) {
- crVal = crVal.subtract(tmp.val);
+ tmp = evalTerm(cpos + 1, ec);
+ if (is_plus) {
+ ratVal = BoundedRational.add(ratVal, tmp.ratVal);
+ if (ratVal == null) {
+ crVal = crVal.add(tmp.val);
+ } else {
+ crVal = ratVal.CRValue();
+ }
} else {
- crVal = ratVal.CRValue();
+ ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
+ if (ratVal == null) {
+ crVal = crVal.subtract(tmp.val);
+ } else {
+ crVal = ratVal.CRValue();
+ }
}
}
cpos = tmp.pos;
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 289e13d..5c96867 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -370,8 +370,12 @@ public class CalculatorResult extends AlignedTextView {
boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
final int minusSpace = negative ? 1 : 0;
final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in); // INVALID_MSD is OK.
- final int decIndex = in.indexOf('.');
String result = in;
+ if (truncated || (negative && result.charAt(0) != '-')) {
+ result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
+ // Ellipsis may be removed again in the type(1) scientific notation case.
+ }
+ final int decIndex = result.indexOf('.');
lastDisplayedOffset[0] = precOffset;
if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
&& msdIndex - decIndex > MAX_LEADING_ZEROES + 1) && precOffset != -1) {
@@ -400,42 +404,38 @@ public class CalculatorResult extends AlignedTextView {
exponent = initExponent + resLen - msdIndex - 1;
hasPoint = true;
}
- if (exponent != 0 || truncated) {
- // Actually add the exponent of either type:
- if (!forcePrecision) {
- int dropDigits; // Digits to drop to make room for exponent.
- if (hasPoint) {
- // Type (1) exponent.
- // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
- dropDigits = expLen(exponent);
- if (dropDigits >= result.length() - 1) {
- // Jumpy is better than no mantissa. Probably impossible anyway.
- dropDigits = Math.max(result.length() - 2, 0);
- }
- } else {
- // Type (2) exponent.
- // Exponent depends on the number of digits we drop, which depends on
- // exponent ...
- for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
- ++dropDigits) {}
- exponent = initExponent + dropDigits;
- if (precOffset - dropDigits > mLsdOffset) {
- // This can happen if e.g. result = 10^40 + 10^10
- // It turns out we would otherwise display ...10e9 because it takes
- // the same amount of space as ...1e10 but shows one more digit.
- // But we don't want to display a trailing zero, even if it's free.
- ++dropDigits;
- ++exponent;
- }
+ // Exponent can't be zero.
+ // Actually add the exponent of either type:
+ if (!forcePrecision) {
+ int dropDigits; // Digits to drop to make room for exponent.
+ if (hasPoint) {
+ // Type (1) exponent.
+ // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
+ dropDigits = expLen(exponent);
+ if (dropDigits >= result.length() - 1) {
+ // Jumpy is better than no mantissa. Probably impossible anyway.
+ dropDigits = Math.max(result.length() - 2, 0);
+ }
+ } else {
+ // Type (2) exponent.
+ // Exponent depends on the number of digits we drop, which depends on
+ // exponent ...
+ for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
+ ++dropDigits) {}
+ exponent = initExponent + dropDigits;
+ if (precOffset - dropDigits > mLsdOffset) {
+ // This can happen if e.g. result = 10^40 + 10^10
+ // It turns out we would otherwise display ...10e9 because it takes
+ // the same amount of space as ...1e10 but shows one more digit.
+ // But we don't want to display a trailing zero, even if it's free.
+ ++dropDigits;
+ ++exponent;
}
- result = result.substring(0, result.length() - dropDigits);
- lastDisplayedOffset[0] -= dropDigits;
}
- result = result + "E" + Integer.toString(exponent);
- } // else don't add zero exponent
- }
- if (truncated || negative && result.charAt(0) != '-') {
- result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
+ result = result.substring(0, result.length() - dropDigits);
+ lastDisplayedOffset[0] -= dropDigits;
+ }
+ result = result + "E" + Integer.toString(exponent);
}
return result;
}
diff --git a/tests/README.txt b/tests/README.txt
index b5e6dfe..304766f 100644
--- a/tests/README.txt
+++ b/tests/README.txt
@@ -39,6 +39,8 @@ Some interesting manual test cases:
-10^30 - 10^10
-1.2x10^-9
-1.2x10^-8
+-1.2x10^-10
+-10^-12
1 - 10^-98
1 - 10^-100
1 - 10^-300