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
This commit is contained in:
@@ -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 { *; }
|
||||
|
||||
@@ -106,6 +106,15 @@
|
||||
android:contentDescription="@string/desc_fun_ln"
|
||||
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"
|
||||
@@ -114,6 +123,15 @@
|
||||
android:contentDescription="@string/desc_fun_log"
|
||||
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"
|
||||
@@ -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>
|
||||
|
||||
@@ -106,6 +106,15 @@
|
||||
android:contentDescription="@string/desc_fun_ln"
|
||||
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"
|
||||
@@ -114,6 +123,15 @@
|
||||
android:contentDescription="@string/desc_fun_log"
|
||||
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"
|
||||
@@ -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>
|
||||
|
||||
@@ -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] -->
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user