blob: 05afb4978d59223aae9eb4dfdc0e0ca73498a85a [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
Hans Boehme4579122015-05-26 17:52:32 -07002 * Copyright (C) 2015 The Android Open Source Project
Hans Boehm84614952014-11-25 18:46:17 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.calculator2;
18
Hans Boehm84614952014-11-25 18:46:17 -080019
20import com.hp.creals.CR;
21import com.hp.creals.UnaryCRFunction;
22import com.hp.creals.PrecisionOverflowError;
23import com.hp.creals.AbortedError;
24
25import android.content.Context;
Hans Boehmc023b732015-04-29 11:30:47 -070026import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080027
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070028import java.math.BigInteger;
29import java.io.DataInput;
30import java.io.DataOutput;
31import java.io.IOException;
32import java.text.DecimalFormatSymbols;
33import java.util.ArrayList;
34import java.util.HashMap;
35import java.util.IdentityHashMap;
36
Hans Boehm84614952014-11-25 18:46:17 -080037// A mathematical expression represented as a sequence of "tokens".
38// Many tokes are represented by button ids for the corresponding operator.
39// Parsed only when we evaluate the expression using the "eval" method.
40class CalculatorExpr {
41 private ArrayList<Token> mExpr; // The actual representation
42 // as a list of tokens. Constant
43 // tokens are always nonempty.
44
45 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
46 private static TokenKind[] tokenKindValues = TokenKind.values();
47 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
48 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
49
50 private static abstract class Token {
51 abstract TokenKind kind();
52 abstract void write(DataOutput out) throws IOException;
53 // Implementation writes kind as Byte followed by
54 // data read by constructor.
55 abstract String toString(Context context);
56 // We need the context to convert button ids to strings.
57 }
58
59 // An operator token
60 private static class Operator extends Token {
61 final int mId; // We use the button resource id
62 Operator(int resId) {
63 mId = resId;
64 }
65 Operator(DataInput in) throws IOException {
66 mId = in.readInt();
67 }
68 @Override
69 void write(DataOutput out) throws IOException {
70 out.writeByte(TokenKind.OPERATOR.ordinal());
71 out.writeInt(mId);
72 }
73 @Override
74 public String toString(Context context) {
75 return KeyMaps.toString(mId, context);
76 }
77 @Override
78 TokenKind kind() { return TokenKind.OPERATOR; }
79 }
80
Hans Boehm682ff5e2015-03-09 14:40:25 -070081 // A (possibly incomplete) numerical constant.
82 // Supports addition and removal of trailing characters; hence mutable.
Hans Boehm84614952014-11-25 18:46:17 -080083 private static class Constant extends Token implements Cloneable {
84 private boolean mSawDecimal;
85 String mWhole; // part before decimal point
86 private String mFraction; // part after decimal point
87
88 Constant() {
89 mWhole = "";
90 mFraction = "";
91 mSawDecimal = false;
92 };
93
94 Constant(DataInput in) throws IOException {
95 mWhole = in.readUTF();
96 mSawDecimal = in.readBoolean();
97 mFraction = in.readUTF();
98 }
99
100 @Override
101 void write(DataOutput out) throws IOException {
102 out.writeByte(TokenKind.CONSTANT.ordinal());
103 out.writeUTF(mWhole);
104 out.writeBoolean(mSawDecimal);
105 out.writeUTF(mFraction);
106 }
107
108 // Given a button press, append corresponding digit.
109 // We assume id is a digit or decimal point.
110 // Just return false if this was the second (or later) decimal point
111 // in this constant.
112 boolean add(int id) {
113 if (id == R.id.dec_point) {
114 if (mSawDecimal) return false;
115 mSawDecimal = true;
116 return true;
117 }
118 int val = KeyMaps.digVal(id);
119 if (mSawDecimal) {
120 mFraction += val;
121 } else {
122 mWhole += val;
123 }
124 return true;
125 }
126
127 // Undo the last add.
128 // Assumes the constant is nonempty.
129 void delete() {
130 if (!mFraction.isEmpty()) {
131 mFraction = mFraction.substring(0, mFraction.length() - 1);
132 } else if (mSawDecimal) {
133 mSawDecimal = false;
134 } else {
135 mWhole = mWhole.substring(0, mWhole.length() - 1);
136 }
137 }
138
139 boolean isEmpty() {
140 return (mSawDecimal == false && mWhole.isEmpty());
141 }
142
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700143 // Produces human-readable string, as typed.
Hans Boehm013969e2015-04-13 20:29:47 -0700144 // Result is internationalized.
Hans Boehm84614952014-11-25 18:46:17 -0800145 @Override
146 public String toString() {
147 String result = mWhole;
148 if (mSawDecimal) {
Hans Boehm013969e2015-04-13 20:29:47 -0700149 result += '.';
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700150 result += mFraction;
151 }
Hans Boehm013969e2015-04-13 20:29:47 -0700152 return KeyMaps.translateResult(result);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700153 }
154
155 // Eliminates leading decimal, which some of our
156 // other packages don't like.
Hans Boehm013969e2015-04-13 20:29:47 -0700157 // Meant for machine consumption:
158 // Doesn't internationalize decimal point or digits.
159 public String toEasyString() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700160 String result = mWhole;
161 if (result.isEmpty()) result = "0";
162 if (mSawDecimal) {
Hans Boehm84614952014-11-25 18:46:17 -0800163 result += '.';
164 result += mFraction;
165 }
166 return result;
167 }
168
Hans Boehm682ff5e2015-03-09 14:40:25 -0700169 public BoundedRational toRational() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700170 String whole = mWhole;
171 if (whole.isEmpty()) whole = "0";
172 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700173 BigInteger den = BigInteger.TEN.pow(mFraction.length());
174 return new BoundedRational(num, den);
175 }
176
Hans Boehm84614952014-11-25 18:46:17 -0800177 @Override
178 String toString(Context context) {
179 return toString();
180 }
181
182 @Override
183 TokenKind kind() { return TokenKind.CONSTANT; }
184
185 // Override clone to make it public
186 @Override
187 public Object clone() {
188 Constant res = new Constant();
189 res.mWhole = mWhole;
190 res.mFraction = mFraction;
191 res.mSawDecimal = mSawDecimal;
192 return res;
193 }
194 }
195
196 // Hash maps used to detect duplicate subexpressions when
197 // we write out CalculatorExprs and read them back in.
198 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
199 new ThreadLocal<IdentityHashMap<CR,Integer>>();
200 // Maps expressions to indices on output
201 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
202 new ThreadLocal<HashMap<Integer,PreEval>>();
203 // Maps expressions to indices on output
204 private static final ThreadLocal<Integer> exprIndex =
205 new ThreadLocal<Integer>();
206
207 static void initExprOutput() {
208 outMap.set(new IdentityHashMap<CR,Integer>());
209 exprIndex.set(Integer.valueOf(0));
210 }
211
212 static void initExprInput() {
213 inMap.set(new HashMap<Integer,PreEval>());
214 }
215
216 // We treat previously evaluated subexpressions as tokens
217 // These are inserted when either:
218 // - We continue an expression after evaluating some of it.
219 // - TODO: When we copy/paste expressions.
220 // The representation includes three different representations
221 // of the expression:
222 // 1) The CR value for use in computation.
223 // 2) The integer value for use in the computations,
224 // if the expression evaluates to an integer.
225 // 3a) The corresponding CalculatorExpr, together with
226 // 3b) The context (currently just deg/rad mode) used to evaluate
227 // the expression.
228 // 4) A short string representation that is used to
229 // Display the expression.
230 //
231 // (3) is present only so that we can persist the object.
232 // (4) is stored explicitly to avoid waiting for recomputation in the UI
233 // thread.
234 private static class PreEval extends Token {
235 final CR mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700236 final BoundedRational mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800237 private final CalculatorExpr mExpr;
238 private final EvalContext mContext;
239 private final String mShortRep;
Hans Boehm013969e2015-04-13 20:29:47 -0700240 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
241 EvalContext ec, String shortRep) {
Hans Boehm84614952014-11-25 18:46:17 -0800242 mValue = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700243 mRatValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800244 mExpr = expr;
245 mContext = ec;
246 mShortRep = shortRep;
247 }
248 // In writing out PreEvals, we are careful to avoid writing
249 // out duplicates. We assume that two expressions are
250 // duplicates if they have the same mVal. This avoids a
251 // potential exponential blow up in certain off cases and
252 // redundant evaluation after reading them back in.
253 // The parameter hash map maps expressions we've seen
254 // before to their index.
255 @Override
256 void write(DataOutput out) throws IOException {
257 out.writeByte(TokenKind.PRE_EVAL.ordinal());
258 Integer index = outMap.get().get(mValue);
259 if (index == null) {
260 int nextIndex = exprIndex.get() + 1;
261 exprIndex.set(nextIndex);
262 outMap.get().put(mValue, nextIndex);
263 out.writeInt(nextIndex);
264 mExpr.write(out);
265 mContext.write(out);
266 out.writeUTF(mShortRep);
267 } else {
268 // Just write out the index
269 out.writeInt(index);
270 }
271 }
272 PreEval(DataInput in) throws IOException {
273 int index = in.readInt();
274 PreEval prev = inMap.get().get(index);
275 if (prev == null) {
276 mExpr = new CalculatorExpr(in);
Hans Boehmc023b732015-04-29 11:30:47 -0700277 mContext = new EvalContext(in, mExpr.mExpr.size());
Hans Boehm84614952014-11-25 18:46:17 -0800278 // Recompute other fields
279 // We currently do this in the UI thread, but we
280 // only create PreEval expressions that were
281 // previously successfully evaluated, and thus
282 // don't diverge. We also only evaluate to a
283 // constructive real, which involves substantial
284 // work only in fairly contrived circumstances.
285 // TODO: Deal better with slow evaluations.
Hans Boehmc023b732015-04-29 11:30:47 -0700286 EvalRet res = null;
287 try {
288 res = mExpr.evalExpr(0, mContext);
289 } catch (SyntaxException e) {
290 // Should be impossible, since we only write out
291 // expressions that can be evaluated.
292 Log.e("Calculator", "Unexpected syntax exception" + e);
293 }
Hans Boehm84614952014-11-25 18:46:17 -0800294 mValue = res.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700295 mRatValue = res.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800296 mShortRep = in.readUTF();
297 inMap.get().put(index, this);
298 } else {
299 mValue = prev.mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700300 mRatValue = prev.mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800301 mExpr = prev.mExpr;
302 mContext = prev.mContext;
303 mShortRep = prev.mShortRep;
304 }
305 }
306 @Override
307 String toString(Context context) {
308 return mShortRep;
309 }
310 @Override
311 TokenKind kind() { return TokenKind.PRE_EVAL; }
312 }
313
314 static Token newToken(DataInput in) throws IOException {
315 TokenKind kind = tokenKindValues[in.readByte()];
316 switch(kind) {
317 case CONSTANT:
318 return new Constant(in);
319 case OPERATOR:
320 return new Operator(in);
321 case PRE_EVAL:
322 return new PreEval(in);
323 default: throw new IOException("Bad save file format");
324 }
325 }
326
327 CalculatorExpr() {
328 mExpr = new ArrayList<Token>();
329 }
330
331 private CalculatorExpr(ArrayList<Token> expr) {
332 mExpr = expr;
333 }
334
335 CalculatorExpr(DataInput in) throws IOException {
336 mExpr = new ArrayList<Token>();
337 int size = in.readInt();
338 for (int i = 0; i < size; ++i) {
339 mExpr.add(newToken(in));
340 }
341 }
342
343 void write(DataOutput out) throws IOException {
344 int size = mExpr.size();
345 out.writeInt(size);
346 for (int i = 0; i < size; ++i) {
347 mExpr.get(i).write(out);
348 }
349 }
350
351 private boolean hasTrailingBinary() {
352 int s = mExpr.size();
353 if (s == 0) return false;
354 Token t = mExpr.get(s-1);
355 if (!(t instanceof Operator)) return false;
356 Operator o = (Operator)t;
357 return (KeyMaps.isBinary(o.mId));
358 }
359
360 // Append press of button with given id to expression.
361 // Returns false and does nothing if this would clearly
362 // result in a syntax error.
363 boolean add(int id) {
364 int s = mExpr.size();
365 int d = KeyMaps.digVal(id);
366 boolean binary = KeyMaps.isBinary(id);
367 if (s == 0 && binary && id != R.id.op_sub) return false;
368 if (binary && hasTrailingBinary()
Hans Boehmc023b732015-04-29 11:30:47 -0700369 && (id != R.id.op_sub || isOperatorUnchecked(s-1, R.id.op_sub))) {
Hans Boehm84614952014-11-25 18:46:17 -0800370 return false;
371 }
372 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
373 if (isConstPiece) {
374 if (s == 0) {
375 mExpr.add(new Constant());
376 s++;
377 } else {
378 Token last = mExpr.get(s-1);
379 if(!(last instanceof Constant)) {
380 if (!(last instanceof Operator)) {
381 return false;
382 }
383 int lastOp = ((Operator)last).mId;
384 if (lastOp == R.id.const_e || lastOp == R.id.const_pi
385 || lastOp == R.id.op_fact
386 || lastOp == R.id.rparen) {
387 // Constant cannot possibly follow; reject immediately
388 return false;
389 }
390 mExpr.add(new Constant());
391 s++;
392 }
393 }
394 return ((Constant)(mExpr.get(s-1))).add(id);
395 } else {
396 mExpr.add(new Operator(id));
397 return true;
398 }
399 }
400
401 // Append the contents of the argument expression.
402 // It is assumed that the argument expression will not change,
403 // and thus its pieces can be reused directly.
404 // TODO: We probably only need this for expressions consisting of
405 // a single PreEval "token", and may want to check that.
406 void append(CalculatorExpr expr2) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700407 // Check that we're not concatenating Constant or PreEval
408 // tokens, since the result would look like a single constant
409 int s = mExpr.size();
Hans Boehm84614952014-11-25 18:46:17 -0800410 int s2 = expr2.mExpr.size();
Hans Boehmfbcef702015-04-27 18:07:47 -0700411 // Check that we're not concatenating Constant or PreEval
412 // tokens, since the result would look like a single constant,
413 // with very mysterious results for the user.
414 if (s != 0 && s2 != 0) {
415 Token last = mExpr.get(s-1);
416 Token first = expr2.mExpr.get(0);
417 if (!(first instanceof Operator) && !(last instanceof Operator)) {
418 // Fudge it by adding an explicit multiplication.
419 // We would have interpreted it as such anyway, and this
420 // makes it recognizable to the user.
421 mExpr.add(new Operator(R.id.op_mul));
422 }
423 }
Hans Boehm84614952014-11-25 18:46:17 -0800424 for (int i = 0; i < s2; ++i) {
425 mExpr.add(expr2.mExpr.get(i));
426 }
427 }
428
429 // Undo the last key addition, if any.
430 void delete() {
431 int s = mExpr.size();
432 if (s == 0) return;
433 Token last = mExpr.get(s-1);
434 if (last instanceof Constant) {
435 Constant c = (Constant)last;
436 c.delete();
437 if (!c.isEmpty()) return;
438 }
439 mExpr.remove(s-1);
440 }
441
442 void clear() {
443 mExpr.clear();
444 }
445
446 boolean isEmpty() {
447 return mExpr.isEmpty();
448 }
449
450 // Returns a logical deep copy of the CalculatorExpr.
451 // Operator and PreEval tokens are immutable, and thus
452 // aren't really copied.
453 public Object clone() {
454 CalculatorExpr res = new CalculatorExpr();
455 for (Token t: mExpr) {
456 if (t instanceof Constant) {
457 res.mExpr.add((Token)(((Constant)t).clone()));
458 } else {
459 res.mExpr.add(t);
460 }
461 }
462 return res;
463 }
464
465 // Am I just a constant?
466 boolean isConstant() {
467 if (mExpr.size() != 1) return false;
468 return mExpr.get(0) instanceof Constant;
469 }
470
471 // Return a new expression consisting of a single PreEval token
472 // representing the current expression.
473 // The caller supplies the value, degree mode, and short
474 // string representation, which must have been previously computed.
475 // Thus this is guaranteed to terminate reasonably quickly.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700476 CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800477 boolean dm, String sr) {
478 CalculatorExpr result = new CalculatorExpr();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700479 Token t = new PreEval(val, ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800480 new CalculatorExpr(
481 (ArrayList<Token>)mExpr.clone()),
Hans Boehmc023b732015-04-29 11:30:47 -0700482 new EvalContext(dm, mExpr.size()), sr);
Hans Boehm84614952014-11-25 18:46:17 -0800483 result.mExpr.add(t);
484 return result;
485 }
486
487 // Internal evaluation functions return an EvalRet triple.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700488 // We compute rational (BoundedRational) results when possible, both as
Hans Boehm84614952014-11-25 18:46:17 -0800489 // a performance optimization, and to detect errors exactly when we can.
490 private class EvalRet {
491 int mPos; // Next position (expression index) to be parsed
492 final CR mVal; // Constructive Real result of evaluating subexpression
Hans Boehm682ff5e2015-03-09 14:40:25 -0700493 final BoundedRational mRatVal; // Exact Rational value or null if
494 // irrational or hard to compute.
495 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm84614952014-11-25 18:46:17 -0800496 mPos = p;
497 mVal = v;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700498 mRatVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800499 }
500 }
501
502 // And take a context argument:
503 private static class EvalContext {
Hans Boehmc023b732015-04-29 11:30:47 -0700504 public final int mPrefixLength; // Length of prefix to evaluate.
505 // Not explicitly saved.
506 public final boolean mDegreeMode;
Hans Boehm84614952014-11-25 18:46:17 -0800507 // If we add any other kinds of evaluation modes, they go here.
Hans Boehmc023b732015-04-29 11:30:47 -0700508 EvalContext(boolean degreeMode, int len) {
Hans Boehm84614952014-11-25 18:46:17 -0800509 mDegreeMode = degreeMode;
Hans Boehmc023b732015-04-29 11:30:47 -0700510 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800511 }
Hans Boehmc023b732015-04-29 11:30:47 -0700512 EvalContext(DataInput in, int len) throws IOException {
Hans Boehm84614952014-11-25 18:46:17 -0800513 mDegreeMode = in.readBoolean();
Hans Boehmc023b732015-04-29 11:30:47 -0700514 mPrefixLength = len;
Hans Boehm84614952014-11-25 18:46:17 -0800515 }
516 void write(DataOutput out) throws IOException {
517 out.writeBoolean(mDegreeMode);
518 }
519 }
520
521 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
522
523 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
524
525 private CR toRadians(CR x, EvalContext ec) {
526 if (ec.mDegreeMode) {
527 return x.multiply(RADIANS_PER_DEGREE);
528 } else {
529 return x;
530 }
531 }
532
533 private CR fromRadians(CR x, EvalContext ec) {
534 if (ec.mDegreeMode) {
535 return x.multiply(DEGREES_PER_RADIAN);
536 } else {
537 return x;
538 }
539 }
540
541 // The following methods can all throw IndexOutOfBoundsException
542 // in the event of a syntax error. We expect that to be caught in
543 // eval below.
544
Hans Boehmc023b732015-04-29 11:30:47 -0700545 private boolean isOperatorUnchecked(int i, int op) {
Hans Boehm84614952014-11-25 18:46:17 -0800546 Token t = mExpr.get(i);
547 if (!(t instanceof Operator)) return false;
548 return ((Operator)(t)).mId == op;
549 }
550
Hans Boehmc023b732015-04-29 11:30:47 -0700551 private boolean isOperator(int i, int op, EvalContext ec) {
552 if (i >= ec.mPrefixLength) return false;
553 return isOperatorUnchecked(i, op);
554 }
555
556 static class SyntaxException extends Exception {
557 public SyntaxException() {
Hans Boehm84614952014-11-25 18:46:17 -0800558 super();
559 }
Hans Boehmc023b732015-04-29 11:30:47 -0700560 public SyntaxException(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800561 super(s);
562 }
563 }
564
565 // The following functions all evaluate some kind of expression
566 // starting at position i in mExpr in a specified evaluation context.
567 // They return both the expression value (as constructive real and,
568 // if applicable, as BigInteger) and the position of the next token
569 // that was not used as part of the evaluation.
Hans Boehmc023b732015-04-29 11:30:47 -0700570 private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800571 Token t = mExpr.get(i);
572 CR value;
573 if (t instanceof Constant) {
574 Constant c = (Constant)t;
Hans Boehm013969e2015-04-13 20:29:47 -0700575 value = CR.valueOf(c.toEasyString(),10);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700576 return new EvalRet(i+1, value, c.toRational());
Hans Boehm84614952014-11-25 18:46:17 -0800577 }
578 if (t instanceof PreEval) {
579 PreEval p = (PreEval)t;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700580 return new EvalRet(i+1, p.mValue, p.mRatValue);
Hans Boehm84614952014-11-25 18:46:17 -0800581 }
582 EvalRet argVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700583 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800584 switch(((Operator)(t)).mId) {
585 case R.id.const_pi:
586 return new EvalRet(i+1, CR.PI, null);
587 case R.id.const_e:
588 return new EvalRet(i+1, CR.valueOf(1).exp(), null);
589 case R.id.op_sqrt:
Hans Boehmfbcef702015-04-27 18:07:47 -0700590 // Seems to have highest precedence.
591 // Does not add implicit paren.
592 // Does seem to accept a leading minus.
Hans Boehmc023b732015-04-29 11:30:47 -0700593 if (isOperator(i+1, R.id.op_sub, ec)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700594 argVal = evalUnary(i+2, ec);
595 ratVal = BoundedRational.sqrt(
596 BoundedRational.negate(argVal.mRatVal));
597 if (ratVal != null) break;
598 return new EvalRet(argVal.mPos,
599 argVal.mVal.negate().sqrt(), null);
600 } else {
601 argVal = evalUnary(i+1, ec);
602 ratVal = BoundedRational.sqrt(argVal.mRatVal);
603 if (ratVal != null) break;
604 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
605 }
Hans Boehm84614952014-11-25 18:46:17 -0800606 case R.id.lparen:
607 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700608 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700609 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800610 case R.id.fun_sin:
611 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700612 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700613 ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700614 : BoundedRational.sin(argVal.mRatVal);
615 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800616 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700617 toRadians(argVal.mVal,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800618 case R.id.fun_cos:
619 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700620 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700621 ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700622 : BoundedRational.cos(argVal.mRatVal);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700623 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800624 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700625 toRadians(argVal.mVal,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800626 case R.id.fun_tan:
627 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700628 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700629 ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700630 : BoundedRational.tan(argVal.mRatVal);
631 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800632 CR argCR = toRadians(argVal.mVal, ec);
633 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700634 argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800635 case R.id.fun_ln:
636 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700637 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700638 ratVal = BoundedRational.ln(argVal.mRatVal);
639 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800640 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
641 case R.id.fun_log:
642 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700643 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700644 ratVal = BoundedRational.log(argVal.mRatVal);
645 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800646 return new EvalRet(argVal.mPos,
647 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
648 null);
649 case R.id.fun_arcsin:
650 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700651 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700652 ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700653 : BoundedRational.asin(argVal.mRatVal);
654 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800655 return new EvalRet(argVal.mPos,
656 fromRadians(UnaryCRFunction
657 .asinFunction.execute(argVal.mVal),ec),
658 null);
659 case R.id.fun_arccos:
660 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700661 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700662 ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700663 : BoundedRational.acos(argVal.mRatVal);
664 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800665 return new EvalRet(argVal.mPos,
666 fromRadians(UnaryCRFunction
667 .acosFunction.execute(argVal.mVal),ec),
668 null);
669 case R.id.fun_arctan:
670 argVal = evalExpr(i+1, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700671 if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
Hans Boehm08e8f322015-04-21 13:18:38 -0700672 ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700673 : BoundedRational.atan(argVal.mRatVal);
674 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800675 return new EvalRet(argVal.mPos,
676 fromRadians(UnaryCRFunction
677 .atanFunction.execute(argVal.mVal),ec),
678 null);
679 default:
Hans Boehmc023b732015-04-29 11:30:47 -0700680 throw new SyntaxException("Unrecognized token in expression");
Hans Boehm84614952014-11-25 18:46:17 -0800681 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700682 // We have a rational value.
683 return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800684 }
685
686 // Compute an integral power of a constructive real.
687 // Unlike the "general" case using logarithms, this handles a negative
688 // base.
689 private static CR pow(CR base, BigInteger exp) {
690 if (exp.compareTo(BigInteger.ZERO) < 0) {
691 return pow(base, exp.negate()).inverse();
692 }
693 if (exp.equals(BigInteger.ONE)) return base;
694 if (exp.and(BigInteger.ONE).intValue() == 1) {
695 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
696 }
697 if (exp.equals(BigInteger.ZERO)) {
698 return CR.valueOf(1);
699 }
700 CR tmp = pow(base, exp.shiftRight(1));
701 return tmp.multiply(tmp);
702 }
703
Hans Boehm682ff5e2015-03-09 14:40:25 -0700704 private static final int TEST_PREC = -100;
705 // Test for integer-ness to 100 bits past binary point.
706 private static final BigInteger MASK =
707 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
708 private static boolean isApprInt(CR x) {
709 BigInteger appr = x.get_appr(TEST_PREC);
710 return appr.and(MASK).signum() == 0;
711 }
712
Hans Boehmc023b732015-04-29 11:30:47 -0700713 private EvalRet evalFactorial(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800714 EvalRet tmp = evalUnary(i, ec);
715 int cpos = tmp.mPos;
716 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700717 BoundedRational ratVal = tmp.mRatVal;
Hans Boehmc023b732015-04-29 11:30:47 -0700718 while (isOperator(cpos, R.id.op_fact, ec)) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700719 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800720 // Assume it was an integer, but we
721 // didn't figure it out.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700722 // KitKat may have used the Gamma function.
723 if (!isApprInt(cval)) {
724 throw new ArithmeticException("factorial(non-integer)");
725 }
726 ratVal = new BoundedRational(cval.BigIntegerValue());
Hans Boehm84614952014-11-25 18:46:17 -0800727 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700728 ratVal = BoundedRational.fact(ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800729 ++cpos;
730 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700731 if (ratVal != null) cval = ratVal.CRValue();
732 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800733 }
734
Hans Boehmc023b732015-04-29 11:30:47 -0700735 private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800736 final EvalRet result1 = evalFactorial(i, ec);
737 int cpos = result1.mPos; // current position
738 CR cval = result1.mVal; // value so far
Hans Boehm682ff5e2015-03-09 14:40:25 -0700739 BoundedRational ratVal = result1.mRatVal; // int value so far
Hans Boehmc023b732015-04-29 11:30:47 -0700740 if (isOperator(cpos, R.id.op_pow, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800741 final EvalRet exp = evalSignedFactor(cpos+1, ec);
742 cpos = exp.mPos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700743 // Try completely rational evaluation first.
744 ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
745 if (ratVal != null) {
746 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800747 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700748 // Power with integer exponent is defined for negative base.
749 // Thus we handle that case separately.
750 // We punt if the exponent is an integer computed from irrational
751 // values. That wouldn't work reliably with floating point either.
752 BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
753 if (int_exp != null) {
754 cval = pow(cval, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800755 } else {
Hans Boehm84614952014-11-25 18:46:17 -0800756 cval = cval.ln().multiply(exp.mVal).exp();
757 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700758 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800759 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700760 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800761 }
762
Hans Boehmc023b732015-04-29 11:30:47 -0700763 private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
764 final boolean negative = isOperator(i, R.id.op_sub, ec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700765 int cpos = negative ? i + 1 : i;
Hans Boehm84614952014-11-25 18:46:17 -0800766 EvalRet tmp = evalFactor(cpos, ec);
767 cpos = tmp.mPos;
Hans Boehm08e8f322015-04-21 13:18:38 -0700768 CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
769 BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
Hans Boehm682ff5e2015-03-09 14:40:25 -0700770 : tmp.mRatVal;
771 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800772 }
773
774 private boolean canStartFactor(int i) {
775 if (i >= mExpr.size()) return false;
776 Token t = mExpr.get(i);
777 if (!(t instanceof Operator)) return true;
778 int id = ((Operator)(t)).mId;
779 if (KeyMaps.isBinary(id)) return false;
780 switch (id) {
781 case R.id.op_fact:
782 case R.id.rparen:
783 return false;
784 default:
785 return true;
786 }
787 }
788
Hans Boehmc023b732015-04-29 11:30:47 -0700789 private EvalRet evalTerm(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800790 EvalRet tmp = evalSignedFactor(i, ec);
791 boolean is_mul = false;
792 boolean is_div = false;
793 int cpos = tmp.mPos; // Current position in expression.
794 CR cval = tmp.mVal; // Current value.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700795 BoundedRational ratVal = tmp.mRatVal; // Current rational value.
Hans Boehmc023b732015-04-29 11:30:47 -0700796 while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
797 || (is_div = isOperator(cpos, R.id.op_div, ec))
Hans Boehm84614952014-11-25 18:46:17 -0800798 || canStartFactor(cpos)) {
799 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700800 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800801 if (is_div) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700802 ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
803 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800804 cval = cval.divide(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700805 } else {
806 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800807 }
808 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700809 ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
810 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800811 cval = cval.multiply(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700812 } else {
813 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800814 }
815 }
816 cpos = tmp.mPos;
817 is_mul = is_div = false;
818 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700819 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800820 }
821
Hans Boehmc023b732015-04-29 11:30:47 -0700822 private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
Hans Boehm84614952014-11-25 18:46:17 -0800823 EvalRet tmp = evalTerm(i, ec);
824 boolean is_plus;
825 int cpos = tmp.mPos;
826 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700827 BoundedRational ratVal = tmp.mRatVal;
Hans Boehmc023b732015-04-29 11:30:47 -0700828 while ((is_plus = isOperator(cpos, R.id.op_add, ec))
829 || isOperator(cpos, R.id.op_sub, ec)) {
Hans Boehm84614952014-11-25 18:46:17 -0800830 tmp = evalTerm(cpos+1, ec);
831 if (is_plus) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700832 ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
833 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800834 cval = cval.add(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700835 } else {
836 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800837 }
838 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700839 ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
840 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800841 cval = cval.subtract(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700842 } else {
843 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800844 }
845 }
846 cpos = tmp.mPos;
847 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700848 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800849 }
850
851 // Externally visible evaluation result.
852 public class EvalResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700853 EvalResult (CR val, BoundedRational ratVal) {
Hans Boehm84614952014-11-25 18:46:17 -0800854 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700855 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800856 }
857 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700858 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800859 }
860
Hans Boehmc023b732015-04-29 11:30:47 -0700861 // Return the starting position of the sequence of trailing operators
862 // that cannot be meaningfully evaluated.
863 private int trailingOpsStart() {
864 int result = mExpr.size();
865 while (result > 0) {
866 Token last = mExpr.get(result - 1);
867 if (!(last instanceof Operator)) break;
868 Operator o = (Operator)last;
869 if (KeyMaps.isSuffix(o.mId) || o.mId == R.id.const_pi
870 || o.mId == R.id.const_e) {
871 break;
872 }
873 --result;
874 }
875 return result;
876 }
877
878 public boolean hasTrailingOperators() {
879 return trailingOpsStart() != mExpr.size();
880 }
881
882 // Is the current expression worth evaluating?
883 public boolean hasInterestingOps() {
884 int last = trailingOpsStart();
885 int first = 0;
886 if (last > first && isOperatorUnchecked(first, R.id.op_sub)) {
887 // Leading minus is not by itself interesting.
888 first++;
889 }
890 for (int i = first; i < last; ++i) {
891 Token t1 = mExpr.get(i);
892 if (!(t1 instanceof Constant)) return true;
893 // We consider preevaluated expressions "interesting",
894 // since the evaluation will usually result in more precision
895 // than the "short representation".
896 }
897 return false;
898 }
899
Hans Boehm84614952014-11-25 18:46:17 -0800900 // Evaluate the entire expression, returning null in the event
901 // of an error.
902 // Not called from the UI thread, but should not be called
903 // concurrently with modifications to the expression.
Hans Boehmc023b732015-04-29 11:30:47 -0700904 EvalResult eval(boolean degreeMode, boolean required) throws SyntaxException
905 // And unchecked exceptions thrown by CR
906 // and BoundedRational.
Hans Boehm84614952014-11-25 18:46:17 -0800907 {
908 try {
Hans Boehmc023b732015-04-29 11:30:47 -0700909 int prefixLen = required ? mExpr.size() : trailingOpsStart();
910 EvalContext ec = new EvalContext(degreeMode, prefixLen);
Hans Boehm84614952014-11-25 18:46:17 -0800911 EvalRet res = evalExpr(0, ec);
Hans Boehmc023b732015-04-29 11:30:47 -0700912 if (res.mPos != prefixLen) {
913 throw new SyntaxException("Failed to parse full expression");
Hans Boehmfbcef702015-04-27 18:07:47 -0700914 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700915 return new EvalResult(res.mVal, res.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800916 } catch (IndexOutOfBoundsException e) {
Hans Boehmc023b732015-04-29 11:30:47 -0700917 throw new SyntaxException("Unexpected expression end");
Hans Boehm84614952014-11-25 18:46:17 -0800918 }
919 }
920
921 // Produce a string representation of the expression itself
922 String toString(Context context) {
923 StringBuilder sb = new StringBuilder();
924 for (Token t: mExpr) {
925 sb.append(t.toString(context));
926 }
927 return sb.toString();
928 }
929}