diff options
author | Hans Boehm <hboehm@google.com> | 2015-06-02 22:13:43 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2015-06-02 22:13:43 +0000 |
commit | 694406b52e3bafa9da4396de836e6214e3a9d799 (patch) | |
tree | f5b35ef4905d0799c0806d8f298e67446696b54a | |
parent | 7531cd7dadfcb9c7df658fb2193c42b65ea0e21f (diff) | |
parent | 4db31b490443e4454d98a5ae2bc44b87149accfe (diff) | |
download | android_packages_apps_ExactCalculator-694406b52e3bafa9da4396de836e6214e3a9d799.tar.gz android_packages_apps_ExactCalculator-694406b52e3bafa9da4396de836e6214e3a9d799.tar.bz2 android_packages_apps_ExactCalculator-694406b52e3bafa9da4396de836e6214e3a9d799.zip |
Merge "Implement percent and new inverse functions" into mnc-dev
-rw-r--r-- | proguard.flags | 2 | ||||
-rw-r--r-- | res/layout/pad_advanced.xml | 27 | ||||
-rw-r--r-- | res/layout/pad_advanced_tablet_port.xml | 27 | ||||
-rw-r--r-- | res/values/strings.xml | 33 | ||||
-rw-r--r-- | src/com/android/calculator2/BoundedRational.java | 19 | ||||
-rw-r--r-- | src/com/android/calculator2/Calculator.java | 10 | ||||
-rw-r--r-- | src/com/android/calculator2/CalculatorExpr.java | 56 | ||||
-rw-r--r-- | src/com/android/calculator2/Evaluator.java | 24 | ||||
-rw-r--r-- | src/com/android/calculator2/KeyMaps.java | 7 | ||||
-rw-r--r-- | tests/README.txt | 2 | ||||
-rw-r--r-- | tests/src/com/android/calculator2/BRTest.java | 7 |
11 files changed, 182 insertions, 32 deletions
diff --git a/proguard.flags b/proguard.flags index 1185cca..0fa387a 100644 --- a/proguard.flags +++ b/proguard.flags @@ -54,3 +54,5 @@ # Some small BoundedRational methods like equals() are not used by the # calculator, but crucial for testing. -keepclassmembers class com.android.calculator2.BoundedRational { *; } +# Need CR comparison operators for testing. +-keepclassmembers class com.hp.creals.CR { *; } diff --git a/res/layout/pad_advanced.xml b/res/layout/pad_advanced.xml index bd2f30a..0d6168a 100644 --- a/res/layout/pad_advanced.xml +++ b/res/layout/pad_advanced.xml @@ -107,6 +107,15 @@ android:text="@string/fun_ln" /> <Button + android:id="@+id/fun_exp" + style="@style/PadButtonStyle.Advanced" + android:layout_row="2" + android:layout_column="0" + android:contentDescription="@string/desc_fun_exp" + android:text="@string/fun_exp" + android:visibility="gone" /> + + <Button android:id="@+id/fun_log" style="@style/PadButtonStyle.Advanced" android:layout_row="2" @@ -115,6 +124,15 @@ android:text="@string/fun_log" /> <Button + android:id="@+id/fun_10pow" + style="@style/PadButtonStyle.Advanced" + android:layout_row="2" + android:layout_column="1" + android:contentDescription="@string/desc_fun_10pow" + android:text="@string/fun_10pow" + android:visibility="gone" /> + + <Button android:id="@+id/op_fact" style="@style/PadButtonStyle.Advanced" android:layout_row="2" @@ -170,4 +188,13 @@ android:contentDescription="@string/desc_op_sqrt" android:text="@string/op_sqrt" /> + <Button + android:id="@+id/op_sqr" + style="@style/PadButtonStyle.Advanced" + android:layout_row="4" + android:layout_column="2" + android:contentDescription="@string/desc_op_sqr" + android:text="@string/op_sqr" + android:visibility="gone" /> + </GridLayout> diff --git a/res/layout/pad_advanced_tablet_port.xml b/res/layout/pad_advanced_tablet_port.xml index 00b0a70..bccc44c 100644 --- a/res/layout/pad_advanced_tablet_port.xml +++ b/res/layout/pad_advanced_tablet_port.xml @@ -107,6 +107,15 @@ android:text="@string/fun_ln" /> <Button + android:id="@+id/fun_exp" + style="@style/PadButtonStyle.Advanced" + android:layout_row="1" + android:layout_column="1" + android:contentDescription="@string/desc_fun_exp" + android:text="@string/fun_exp" + android:visibility="gone" /> + + <Button android:id="@+id/fun_log" style="@style/PadButtonStyle.Advanced" android:layout_row="1" @@ -115,6 +124,15 @@ android:text="@string/fun_log" /> <Button + android:id="@+id/fun_10pow" + style="@style/PadButtonStyle.Advanced" + android:layout_row="1" + android:layout_column="2" + android:contentDescription="@string/desc_fun_10pow" + android:text="@string/fun_10pow" + android:visibility="gone" /> + + <Button android:id="@+id/op_fact" style="@style/PadButtonStyle.Advanced" android:layout_row="1" @@ -170,4 +188,13 @@ android:contentDescription="@string/desc_op_sqrt" android:text="@string/op_sqrt" /> + <Button + android:id="@+id/op_sqr" + style="@style/PadButtonStyle.Advanced" + android:layout_row="2" + android:layout_column="4" + android:contentDescription="@string/desc_op_sqr" + android:text="@string/op_sqr" + android:visibility="gone" /> + </GridLayout> diff --git a/res/values/strings.xml b/res/values/strings.xml index 3dad1c0..f44ad2d 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -78,23 +78,37 @@ <!-- Subtraction operator (e.g. "1 - 2"). [CHAR_LIMIT=1] --> <string name="op_sub" translatable="false">−</string> - <!-- Abbrev. name of cosine function (e.g. "cos(π)". [CHAR_LIMIT=4] --> + <!-- Abbrev. name of cosine function (e.g. "cos(π)"). [CHAR_LIMIT=4] --> <string name="fun_cos" translatable="false">cos</string> <!-- Natural logarithm function (e.g. "ln(2)"). [CHAR_LIMIT=4] --> <string name="fun_ln" translatable="false">ln</string> <!-- Logarithm function (e.g. "log(10)"). [CHAR_LIMIT=4] --> <string name="fun_log" translatable="false">log</string> - <!-- Abbrev. name of sine function (e.g. "sin(π)". [CHAR_LIMIT=4] --> + <!-- Abbrev. name of sine function (e.g. "sin(π)"). [CHAR_LIMIT=4] --> <string name="fun_sin" translatable="false">sin</string> - <!-- Abbrev. name of tangent function (e.g. "tan(π)". [CHAR_LIMIT=4] --> + <!-- Abbrev. name of tangent function (e.g. "tan(π)"). [CHAR_LIMIT=4] --> <string name="fun_tan" translatable="false">tan</string> - <!-- Abbrev. name of cosine function (e.g. "arccos(π)". [CHAR_LIMIT=5] --> + <!-- Abbrev. name of cosine function (e.g. "arccos(π)"). [CHAR_LIMIT=5] --> <string name="fun_arccos" translatable="false">cos\u207B\u00B9</string> - <!-- Abbrev. name of sine function (e.g. "arcsin(π)". [CHAR_LIMIT=5] --> + <!-- Abbrev. name of sine function (e.g. "arcsin(π)"). [CHAR_LIMIT=5] --> <string name="fun_arcsin" translatable="false">sin\u207B\u00B9</string> - <!-- Abbrev. name of tangent function (e.g. "arctan(π)". [CHAR_LIMIT=5] --> + <!-- Abbrev. name of tangent function (e.g. "arctan(π)"). [CHAR_LIMIT=5] --> <string name="fun_arctan" translatable="false">tan\u207B\u00B9</string> + <!-- Abbrev. name of base 10 exponential function (e.g. "10^6"). [CHAR_LIMIT=5] --> + <string name="fun_10pow" translatable="false">10\u02E3</string> + <!-- Abbrev. name of exponential function (e.g. "e^6"). [CHAR_LIMIT=5] --> + <string name="fun_exp" translatable="false">e\u02E3</string> + <!-- Abbrev. name of suffix square function on key (e.g. "17^2"). [CHAR_LIMIT=5] --> + <string name="op_sqr" translatable="false">x\u00B2</string> + <!-- + Abbrev. name of suffix square function in formula. + "^2" does not work, since it blends into a later constant. + --> + <string name="squared" translatable="false">²</string> + <!-- Abbrev. name of exponential function in formula. --> + <string name="exponential" translatable = "false">exp</string> + <!-- Abbrev. name of degree mode [CHAR_LIMIT=4] --> <string name="mode_deg">deg</string> <!-- Abbrev. name of radian mode. [CHAR_LIMIT=4] --> @@ -139,6 +153,13 @@ <!-- Content description for 'arctan' button. [CHAR_LIMIT=NONE] --> <string name="desc_fun_arctan">inverse tangent</string> + <!-- Content description for 10^ button. [CHAR_LIMIT=NONE] --> + <string name="desc_fun_10pow">ten to the power of</string> + <!-- Content description for e^ button. [CHAR_LIMIT=NONE] --> + <string name="desc_fun_exp">exponential function</string> + <!-- Content description for ^2 button. [CHAR_LIMIT=NONE] --> + <string name="desc_op_sqr">squared</string> + <!-- Content description for '+' button. [CHAR_LIMIT=NONE] --> <string name="desc_op_add">plus</string> <!-- Content description for '÷' button. [CHAR_LIMIT=NONE] --> diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java index ee2ee92..a6dd6d9 100644 --- a/src/com/android/calculator2/BoundedRational.java +++ b/src/com/android/calculator2/BoundedRational.java @@ -229,6 +229,14 @@ public class BoundedRational { return null; } + private static BoundedRational map0to1(BoundedRational r) { + if (r == null) return null; + if (r.mNum.equals(BigInteger.ZERO)) { + return ONE; + } + return null; + } + private static BoundedRational map1to0(BoundedRational r) { if (r == null) return null; if (r.mNum.equals(r.mDen)) { @@ -345,12 +353,7 @@ public class BoundedRational { } public static BoundedRational cos(BoundedRational r) { - // Maps 0 to 1, null otherwise - if (r == null) return null; - if (r.mNum.equals(BigInteger.ZERO)) { - return ONE; - } - return null; + return map0to1(r); } public static BoundedRational degreeCos(BoundedRational r) { @@ -403,6 +406,10 @@ public class BoundedRational { return map1to0(r); } + public static BoundedRational exp(BoundedRational r) { + return map0to1(r); + } + // Return the base 10 log of n, if n is a power of 10, -1 otherwise. // n must be positive. private static long b10Log(BigInteger n) { diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java index 0970174..b2cf477 100644 --- a/src/com/android/calculator2/Calculator.java +++ b/src/com/android/calculator2/Calculator.java @@ -227,12 +227,18 @@ public class Calculator extends Activity mInvertibleButtons = new View[] { findViewById(R.id.fun_sin), findViewById(R.id.fun_cos), - findViewById(R.id.fun_tan) + findViewById(R.id.fun_tan), + findViewById(R.id.fun_ln), + findViewById(R.id.fun_log), + findViewById(R.id.op_sqrt) }; mInverseButtons = new View[] { findViewById(R.id.fun_arcsin), findViewById(R.id.fun_arccos), - findViewById(R.id.fun_arctan) + findViewById(R.id.fun_arctan), + findViewById(R.id.fun_exp), + findViewById(R.id.fun_10pow), + findViewById(R.id.op_sqr) }; mEvaluator = new Evaluator(this, mResultText); diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java index e5be4b9..6771b52 100644 --- a/src/com/android/calculator2/CalculatorExpr.java +++ b/src/com/android/calculator2/CalculatorExpr.java @@ -582,7 +582,7 @@ class CalculatorExpr { case R.id.const_pi: return new EvalRet(i+1, CR.PI, null); case R.id.const_e: - return new EvalRet(i+1, CR.valueOf(1).exp(), null); + return new EvalRet(i+1, REAL_E, null); case R.id.op_sqrt: // Seems to have highest precedence. // Does not add implicit paren. @@ -635,6 +635,12 @@ class CalculatorExpr { ratVal = BoundedRational.ln(argVal.mRatVal); if (ratVal != null) break; return new EvalRet(argVal.mPos, argVal.mVal.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); case R.id.fun_log: argVal = evalExpr(i+1, ec); if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++; @@ -702,35 +708,59 @@ class CalculatorExpr { // 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 boolean isApprInt(CR x) { BigInteger appr = x.get_appr(TEST_PREC); return appr.and(MASK).signum() == 0; } - private EvalRet evalFactorial(int i, EvalContext ec) throws SyntaxException { + 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; - while (isOperator(cpos, R.id.op_fact, ec)) { - if (ratVal == null) { - // Assume it was an integer, but we - // didn't figure it out. - // KitKat may have used the Gamma function. - if (!isApprInt(cval)) { - throw new ArithmeticException("factorial(non-integer)"); + boolean isFact; + boolean isSquared = false; + while ((isFact = isOperator(cpos, R.id.op_fact, ec)) || + (isSquared = isOperator(cpos, R.id.op_sqr, ec)) || + isOperator(cpos, R.id.op_pct, ec)) { + if (isFact) { + if (ratVal == null) { + // Assume it was an integer, but we + // didn't figure it out. + // KitKat may have used the Gamma function. + if (!isApprInt(cval)) { + throw new ArithmeticException("factorial(non-integer)"); + } + ratVal = new BoundedRational(cval.BigIntegerValue()); + } + ratVal = BoundedRational.fact(ratVal); + cval = ratVal.CRValue(); + } else if (isSquared) { + ratVal = BoundedRational.multiply(ratVal, ratVal); + if (ratVal == null) { + cval = cval.multiply(cval); + } else { + cval = ratVal.CRValue(); + } + } else /* percent */ { + ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH); + if (ratVal == null) { + cval = cval.multiply(REAL_ONE_HUNDREDTH); + } else { + cval = ratVal.CRValue(); } - ratVal = new BoundedRational(cval.BigIntegerValue()); } - ratVal = BoundedRational.fact(ratVal); ++cpos; } - if (ratVal != null) cval = ratVal.CRValue(); return new EvalRet(cpos, cval, ratVal); } private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException { - final EvalRet result1 = evalFactorial(i, ec); + 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 diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java index 4ddaf15..9b9e830 100644 --- a/src/com/android/calculator2/Evaluator.java +++ b/src/com/android/calculator2/Evaluator.java @@ -776,10 +776,15 @@ class Evaluator { // syntax issues, and the expression is unchanged. // Return true otherwise. boolean append(int id) { - mChangedValue = (KeyMaps.digVal(id) != KeyMaps.NOT_DIGIT - || KeyMaps.isSuffix(id) - || id == R.id.const_pi || id == R.id.const_e); - return mExpr.add(id); + if (id == R.id.fun_10pow) { + add10pow(); // Handled as macro expansion. + return true; + } else { + mChangedValue = (KeyMaps.digVal(id) != KeyMaps.NOT_DIGIT + || KeyMaps.isSuffix(id) + || id == R.id.const_pi || id == R.id.const_e); + return mExpr.add(id); + } } void delete() { @@ -865,6 +870,17 @@ class Evaluator { mExpr.append(mSaved); } + // 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); + ten.add(R.id.digit_0); + mChangedValue = true; // For consistency. Reevaluation is probably not useful. + mExpr.append(ten); + 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. diff --git a/src/com/android/calculator2/KeyMaps.java b/src/com/android/calculator2/KeyMaps.java index e3b84e7..5385424 100644 --- a/src/com/android/calculator2/KeyMaps.java +++ b/src/com/android/calculator2/KeyMaps.java @@ -67,6 +67,9 @@ public class KeyMaps { return context.getString(R.string.fun_ln) + context.getString(R.string.lparen); case R.id.fun_log: return context.getString(R.string.fun_log) + context.getString(R.string.lparen); + case R.id.fun_exp: + // Button label doesn't work. + return context.getString(R.string.exponential) + context.getString(R.string.lparen); case R.id.lparen: return context.getString(R.string.lparen); case R.id.rparen: @@ -79,6 +82,9 @@ public class KeyMaps { return context.getString(R.string.op_div); case R.id.op_add: return context.getString(R.string.op_add); + case R.id.op_sqr: + // Button label doesn't work. + return context.getString(R.string.squared); case R.id.op_sub: return context.getString(R.string.op_sub); case R.id.dec_point: @@ -132,6 +138,7 @@ public class KeyMaps { switch (id) { case R.id.op_fact: case R.id.op_pct: + case R.id.op_sqr: return true; default: return false; diff --git a/tests/README.txt b/tests/README.txt index 3069de8..b1cd968 100644 --- a/tests/README.txt +++ b/tests/README.txt @@ -2,7 +2,7 @@ Run on Android with 1) Build the tests. 2) Install the calculator with -adb install <tree root>/out/target/product/generic/data/app/ExactCalculatorTests/ExactCalculator.apk +adb install <tree root>/out/target/product/generic/data/app/ExactCalculator/ExactCalculator.apk 3) adb install <tree root>/out/target/product/generic/data/app/ExactCalculatorTests/ExactCalculatorTests.apk 4) adb shell am instrument -w com.android.exactcalculator.tests/android.test.InstrumentationTestRunner diff --git a/tests/src/com/android/calculator2/BRTest.java b/tests/src/com/android/calculator2/BRTest.java index 1cd56a5..29a5ae9 100644 --- a/tests/src/com/android/calculator2/BRTest.java +++ b/tests/src/com/android/calculator2/BRTest.java @@ -92,6 +92,13 @@ public class BRTest extends TestCase { } catch (ArithmeticException ignored) { check((long_x - 90) % 180 == 0, "exception on defined tan: " + x); } + if (x.compareTo(BoundedRational.THIRTY) <= 0 + && x.compareTo(BoundedRational.MINUS_THIRTY) >= 0) { + checkWeakEq(BoundedRational.exp(x), xAsCR.exp(), "exp:" + x); + checkWeakEq(BoundedRational.pow(BR_15, x), + CR.valueOf(15).ln().multiply(xAsCR).exp(), + "pow(15,x):" + x); + } if (x.compareTo(BoundedRational.ONE) <= 0 && x.compareTo(BoundedRational.MINUS_ONE) >= 0) { checkWeakEq(BoundedRational.asin(x), ASIN.execute(xAsCR), |