diff options
-rw-r--r-- | src/com/android/calculator2/CalculatorExpr.java | 95 | ||||
-rw-r--r-- | src/com/android/calculator2/CalculatorResult.java | 70 | ||||
-rw-r--r-- | tests/README.txt | 2 |
3 files changed, 119 insertions, 48 deletions
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 |