summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHans Boehm <hboehm@google.com>2015-05-31 12:19:05 -0700
committerHans Boehm <hboehm@google.com>2015-06-02 11:51:09 -0700
commit4db31b490443e4454d98a5ae2bc44b87149accfe (patch)
tree9843640804505c092ea1f883928b5871f7b56591
parente125edff83b7f73cadaa7a0a007cccec6bd059b2 (diff)
downloadandroid_packages_apps_ExactCalculator-4db31b490443e4454d98a5ae2bc44b87149accfe.tar.gz
android_packages_apps_ExactCalculator-4db31b490443e4454d98a5ae2bc44b87149accfe.tar.bz2
android_packages_apps_ExactCalculator-4db31b490443e4454d98a5ae2bc44b87149accfe.zip
Implement percent and new inverse functions
Bug: 21493470 Add x^2 10^x and e^x functions, to make the recently added INV key work as expected. Implement % functionality. 10^x is essentially just macro expansions for now. % and x^2 need trivial evaluator support to provide reasonable display syntax. We decided to add evaluator support for exp() as well. Add corresponding exp() support to BoundedRational and its tests. Tiny incidental changes for problems uncovered in the process: Fix bug in tests/README.txt Evaluate the constant e only once. Add one more power test along with the exp() test. Fix proguard.flags so BRTest runs again. Change-Id: I26cfcaf6d99aeec11387297cc5586e2ddcab6add
-rw-r--r--proguard.flags2
-rw-r--r--res/layout/pad_advanced.xml27
-rw-r--r--res/layout/pad_advanced_tablet_port.xml27
-rw-r--r--res/values/strings.xml33
-rw-r--r--src/com/android/calculator2/BoundedRational.java19
-rw-r--r--src/com/android/calculator2/Calculator.java10
-rw-r--r--src/com/android/calculator2/CalculatorExpr.java56
-rw-r--r--src/com/android/calculator2/Evaluator.java24
-rw-r--r--src/com/android/calculator2/KeyMaps.java7
-rw-r--r--tests/README.txt2
-rw-r--r--tests/src/com/android/calculator2/BRTest.java7
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),