Commit 8f4a8c73 authored by Hans Boehm's avatar Hans Boehm Committed by Android Git Automerger
Browse files

am 0b9806f6: Support pasting of scientific notation numbers

* commit '0b9806f6':
  Support pasting of scientific notation numbers
parents 63b08564 0b9806f6
......@@ -903,7 +903,7 @@ public class Calculator extends Activity
* are added to mUnprocessedChars, which is presumed to immediately precede the newly
* added characters.
* @param moreChars Characters to be added.
* @param explicit These characters were explicitly typed by the user.
* @param explicit These characters were explicitly typed by the user, not pasted.
*/
private void addChars(String moreChars, boolean explicit) {
if (mUnprocessedChars != null) {
......@@ -911,9 +911,33 @@ public class Calculator extends Activity
}
int current = 0;
int len = moreChars.length();
boolean lastWasDigit = false;
while (current < len) {
char c = moreChars.charAt(current);
int k = KeyMaps.keyForChar(c);
if (!explicit) {
int expEnd;
if (lastWasDigit && current !=
(expEnd = Evaluator.exponentEnd(moreChars, current))) {
// Process scientific notation with 'E' when pasting, in spite of ambiguity
// with base of natural log.
// Otherwise the 10^x key is the user's friend.
mEvaluator.addExponent(moreChars, current, expEnd);
current = expEnd;
lastWasDigit = false;
continue;
} else {
boolean isDigit = KeyMaps.digVal(k) != KeyMaps.NOT_DIGIT;
if (current == 0 && (isDigit || k == R.id.dec_point)
&& mEvaluator.getExpr().hasTrailingConstant()) {
// Refuse to concatenate pasted content to trailing constant.
// This makes pasting of calculator results more consistent, whether or
// not the old calculator instance is still around.
addKeyToExpr(R.id.op_mul);
}
lastWasDigit = (isDigit || lastWasDigit && k == R.id.dec_point);
}
}
if (k != View.NO_ID) {
mCurrentButton = findViewById(k);
if (explicit) {
......
......@@ -79,19 +79,22 @@ class CalculatorExpr {
// Supports addition and removal of trailing characters; hence mutable.
private static class Constant extends Token implements Cloneable {
private boolean mSawDecimal;
String mWhole; // part before decimal point
private String mFraction; // part after decimal point
String mWhole; // String preceding decimal point.
private String mFraction; // String after decimal point.
private int mExponent; // Explicit exponent, only generated through addExponent.
Constant() {
mWhole = "";
mFraction = "";
mSawDecimal = false;
mSawDecimal = false;
mExponent = 0;
};
Constant(DataInput in) throws IOException {
mWhole = in.readUTF();
mSawDecimal = in.readBoolean();
mFraction = in.readUTF();
mExponent = in.readInt();
}
@Override
......@@ -100,19 +103,33 @@ class CalculatorExpr {
out.writeUTF(mWhole);
out.writeBoolean(mSawDecimal);
out.writeUTF(mFraction);
out.writeInt(mExponent);
}
// Given a button press, append corresponding digit.
// We assume id is a digit or decimal point.
// Just return false if this was the second (or later) decimal point
// in this constant.
// Assumes that this constant does not have an exponent.
boolean add(int id) {
if (id == R.id.dec_point) {
if (mSawDecimal) return false;
if (mSawDecimal || mExponent != 0) return false;
mSawDecimal = true;
return true;
}
int val = KeyMaps.digVal(id);
if (mExponent != 0) {
if (Math.abs(mExponent) <= 10000) {
if (mExponent > 0) {
mExponent = 10 * mExponent + val;
} else {
mExponent = 10 * mExponent - val;
}
return true;
} else { // Too large; refuse
return false;
}
}
if (mSawDecimal) {
mFraction += val;
} else {
......@@ -121,10 +138,18 @@ class CalculatorExpr {
return true;
}
void addExponent(int exp) {
// Note that adding a 0 exponent is a no-op. That's OK.
mExponent = exp;
}
// Undo the last add.
// Assumes the constant is nonempty.
void delete() {
if (!mFraction.isEmpty()) {
if (mExponent != 0) {
mExponent /= 10;
// Once zero, it can only be added back with addExponent.
} else if (!mFraction.isEmpty()) {
mFraction = mFraction.substring(0, mFraction.length() - 1);
} else if (mSawDecimal) {
mSawDecimal = false;
......@@ -146,28 +171,24 @@ class CalculatorExpr {
result += '.';
result += mFraction;
}
return KeyMaps.translateResult(result);
}
// Eliminates leading decimal, which some of our
// other packages don't like.
// Meant for machine consumption:
// Doesn't internationalize decimal point or digits.
public String toEasyString() {
String result = mWhole;
if (result.isEmpty()) result = "0";
if (mSawDecimal) {
result += '.';
result += mFraction;
if (mExponent != 0) {
result += "E" + mExponent;
}
return result;
return KeyMaps.translateResult(result);
}
// Return non-null BoundedRational representation.
public BoundedRational toRational() {
String whole = mWhole;
if (whole.isEmpty()) whole = "0";
BigInteger num = new BigInteger(whole + mFraction);
BigInteger den = BigInteger.TEN.pow(mFraction.length());
if (mExponent > 0) {
num = num.multiply(BigInteger.TEN.pow(mExponent));
}
if (mExponent < 0) {
den = den.multiply(BigInteger.TEN.pow(-mExponent));
}
return new BoundedRational(num, den);
}
......@@ -186,6 +207,7 @@ class CalculatorExpr {
res.mWhole = mWhole;
res.mFraction = mFraction;
res.mSawDecimal = mSawDecimal;
res.mExponent = mExponent;
return res;
}
}
......@@ -350,6 +372,15 @@ class CalculatorExpr {
}
}
boolean hasTrailingConstant() {
int s = mExpr.size();
if (s == 0) {
return false;
}
Token t = mExpr.get(s-1);
return t instanceof Constant;
}
private boolean hasTrailingBinary() {
int s = mExpr.size();
if (s == 0) return false;
......@@ -409,6 +440,15 @@ class CalculatorExpr {
}
}
/**
* Add exponent to the constant at the end of the expression.
* Assumes there is a constant at the end of the expression.
*/
void addExponent(int exp) {
Token lastTok = mExpr.get(mExpr.size() - 1);
((Constant) lastTok).addExponent(exp);
}
/**
* Remove trailing op_add and op_sub operators.
*/
......@@ -595,18 +635,19 @@ class CalculatorExpr {
// that was not used as part of the evaluation.
private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Token t = mExpr.get(i);
BoundedRational ratVal;
CR value;
if (t instanceof Constant) {
Constant c = (Constant)t;
value = CR.valueOf(c.toEasyString(),10);
return new EvalRet(i+1, value, c.toRational());
ratVal = c.toRational();
value = ratVal.CRValue();
return new EvalRet(i+1, value, ratVal);
}
if (t instanceof PreEval) {
PreEval p = (PreEval)t;
return new EvalRet(i+1, p.mValue, p.mRatValue);
}
EvalRet argVal;
BoundedRational ratVal;
switch(((Operator)(t)).mId) {
case R.id.const_pi:
return new EvalRet(i+1, CR.PI, null);
......
......@@ -349,7 +349,7 @@ public class CalculatorResult extends AlignedTextView {
// We add ellipses and exponents in a way that leaves most digits in the position they
// would have been in had we not done so.
// This minimizes jumps as a result of scrolling. Result is NOT internationalized,
// uses "e" for exponent.
// uses "E" for exponent.
public String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
final int minusSpace = negative ? 1 : 0;
......@@ -415,7 +415,7 @@ public class CalculatorResult extends AlignedTextView {
result = result.substring(0, result.length() - dropDigits);
lastDisplayedOffset[0] -= dropDigits;
}
result = result + "e" + Integer.toString(exponent);
result = result + "E" + Integer.toString(exponent);
} // else don't add zero exponent
}
if (truncated || negative && result.charAt(0) != '-') {
......@@ -504,7 +504,7 @@ public class CalculatorResult extends AlignedTextView {
int maxChars = getMaxChars();
int lastDisplayedOffset[] = new int[1];
String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset, false);
int expIndex = result.indexOf('e');
int expIndex = result.indexOf('E');
result = KeyMaps.translateResult(result);
if (expIndex > 0 && result.indexOf('.') == -1) {
// Gray out exponent if used as position indicator
......
......@@ -613,7 +613,7 @@ class Evaluator {
}
if (totalDigits <= SHORT_TARGET_LENGTH - 3) {
return negativeSign + cache.charAt(msdIndex) + "."
+ cache.substring(msdIndex + 1, lsdIndex + 1) + "e" + exponent;
+ cache.substring(msdIndex + 1, lsdIndex + 1) + "E" + exponent;
}
}
// We need to abbreviate.
......@@ -624,7 +624,7 @@ class Evaluator {
// Need abbreviation + exponent
return negativeSign + cache.charAt(msdIndex) + "."
+ cache.substring(msdIndex + 1, msdIndex + SHORT_TARGET_LENGTH - negative - 4)
+ KeyMaps.ELLIPSIS + "e" + exponent;
+ KeyMaps.ELLIPSIS + "E" + exponent;
}
// Return the most significant digit position in the given string
......@@ -1008,4 +1008,55 @@ class Evaluator {
return mExpr;
}
private static final int MAX_EXP_CHARS = 8;
/**
* Return the index of the character after the exponent starting at s[offset].
* Return offset if there is no exponent at that position.
* Exponents have syntax E[-]digit* .
* "E2" and "E-2" are valid. "E+2" and "e2" are not.
* We allow any Unicode digits, and either of the commonly used minus characters.
*/
static int exponentEnd(String s, int offset) {
int i = offset;
int len = s.length();
if (i >= len - 1 || s.charAt(i) != 'E') {
return offset;
}
++i;
if (KeyMaps.keyForChar(s.charAt(i)) == R.id.op_sub) {
++i;
}
if (i == len || i > offset + MAX_EXP_CHARS || !Character.isDigit(s.charAt(i))) {
return offset;
}
++i;
while (i < len && Character.isDigit(s.charAt(i))) {
++i;
}
return i;
}
/**
* Add the exponent represented by s[begin..end) to the constant at the end of current
* expression.
* The end of the current expression must be a constant.
* Exponents have the same syntax as for exponentEnd().
*/
void addExponent(String s, int begin, int end) {
int sign = 1;
int exp = 0;
int i = begin + 1;
// We do the decimal conversion ourselves to exactly match exponentEnd() conventions
// and handle various kinds of digits on input. Also avoids allocation.
if (KeyMaps.keyForChar(s.charAt(i)) == R.id.op_sub) {
sign = -1;
++i;
}
for (; i < end; ++i) {
exp = 10 * exp + Character.digit(s.charAt(i), 10);
}
mExpr.addExponent(sign * exp);
mChangedValue = true;
}
}
......@@ -184,6 +184,8 @@ public class KeyMaps {
public static final String ELLIPSIS = "\u2026";
public static final char MINUS_SIGN = '\u2212';
/**
* Map key id to digit or NOT_DIGIT
* Pure function.
......@@ -309,12 +311,15 @@ public class KeyMaps {
case ',':
return R.id.dec_point;
case '-':
case MINUS_SIGN:
return R.id.op_sub;
case '+':
return R.id.op_add;
case '*':
case '\u00D7': // MULTIPLICATION SIGN
return R.id.op_mul;
case '/':
case '\u00F7': // DIVISION SIGN
return R.id.op_div;
// We no longer localize function names, so they can't start with an 'e' or 'p'.
case 'e':
......@@ -337,7 +342,7 @@ public class KeyMaps {
if (c == mDecimalPt) return R.id.dec_point;
if (c == mPiChar) return R.id.const_pi;
// pi is not translated, but it might be typable on a Greek keyboard,
// so we check ...
// or pasted in, so we check ...
return View.NO_ID;
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment