blob: 90cd8f117489c71be0b3f85b88b95d986e009f2c [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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;
26
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070027import java.math.BigInteger;
28import java.io.DataInput;
29import java.io.DataOutput;
30import java.io.IOException;
31import java.text.DecimalFormatSymbols;
32import java.util.ArrayList;
33import java.util.HashMap;
34import java.util.IdentityHashMap;
35
Hans Boehm84614952014-11-25 18:46:17 -080036// A mathematical expression represented as a sequence of "tokens".
37// Many tokes are represented by button ids for the corresponding operator.
38// Parsed only when we evaluate the expression using the "eval" method.
39class CalculatorExpr {
40 private ArrayList<Token> mExpr; // The actual representation
41 // as a list of tokens. Constant
42 // tokens are always nonempty.
43
44 private static enum TokenKind { CONSTANT, OPERATOR, PRE_EVAL };
45 private static TokenKind[] tokenKindValues = TokenKind.values();
46 private final static BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
47 private final static BigInteger BIG_BILLION = BigInteger.valueOf(1000000000);
48
49 private static abstract class Token {
50 abstract TokenKind kind();
51 abstract void write(DataOutput out) throws IOException;
52 // Implementation writes kind as Byte followed by
53 // data read by constructor.
54 abstract String toString(Context context);
55 // We need the context to convert button ids to strings.
56 }
57
58 // An operator token
59 private static class Operator extends Token {
60 final int mId; // We use the button resource id
61 Operator(int resId) {
62 mId = resId;
63 }
64 Operator(DataInput in) throws IOException {
65 mId = in.readInt();
66 }
67 @Override
68 void write(DataOutput out) throws IOException {
69 out.writeByte(TokenKind.OPERATOR.ordinal());
70 out.writeInt(mId);
71 }
72 @Override
73 public String toString(Context context) {
74 return KeyMaps.toString(mId, context);
75 }
76 @Override
77 TokenKind kind() { return TokenKind.OPERATOR; }
78 }
79
Hans Boehm682ff5e2015-03-09 14:40:25 -070080 // A (possibly incomplete) numerical constant.
81 // Supports addition and removal of trailing characters; hence mutable.
Hans Boehm84614952014-11-25 18:46:17 -080082 private static class Constant extends Token implements Cloneable {
83 private boolean mSawDecimal;
84 String mWhole; // part before decimal point
85 private String mFraction; // part after decimal point
86
87 Constant() {
88 mWhole = "";
89 mFraction = "";
90 mSawDecimal = false;
91 };
92
93 Constant(DataInput in) throws IOException {
94 mWhole = in.readUTF();
95 mSawDecimal = in.readBoolean();
96 mFraction = in.readUTF();
97 }
98
99 @Override
100 void write(DataOutput out) throws IOException {
101 out.writeByte(TokenKind.CONSTANT.ordinal());
102 out.writeUTF(mWhole);
103 out.writeBoolean(mSawDecimal);
104 out.writeUTF(mFraction);
105 }
106
107 // Given a button press, append corresponding digit.
108 // We assume id is a digit or decimal point.
109 // Just return false if this was the second (or later) decimal point
110 // in this constant.
111 boolean add(int id) {
112 if (id == R.id.dec_point) {
113 if (mSawDecimal) return false;
114 mSawDecimal = true;
115 return true;
116 }
117 int val = KeyMaps.digVal(id);
118 if (mSawDecimal) {
119 mFraction += val;
120 } else {
121 mWhole += val;
122 }
123 return true;
124 }
125
126 // Undo the last add.
127 // Assumes the constant is nonempty.
128 void delete() {
129 if (!mFraction.isEmpty()) {
130 mFraction = mFraction.substring(0, mFraction.length() - 1);
131 } else if (mSawDecimal) {
132 mSawDecimal = false;
133 } else {
134 mWhole = mWhole.substring(0, mWhole.length() - 1);
135 }
136 }
137
138 boolean isEmpty() {
139 return (mSawDecimal == false && mWhole.isEmpty());
140 }
141
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700142 // Produces human-readable string, as typed.
143 // Decimal separator is mapped to canonical character.
Hans Boehm84614952014-11-25 18:46:17 -0800144 @Override
145 public String toString() {
146 String result = mWhole;
147 if (mSawDecimal) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700148 result += DecimalFormatSymbols.getInstance()
149 .getDecimalSeparator();
150 result += mFraction;
151 }
152 return result;
153 }
154
155 // Eliminates leading decimal, which some of our
156 // other packages don't like.
157 // Doesn't internationalize decimnal point.
158 public String toNiceString() {
159 String result = mWhole;
160 if (result.isEmpty()) result = "0";
161 if (mSawDecimal) {
Hans Boehm84614952014-11-25 18:46:17 -0800162 result += '.';
163 result += mFraction;
164 }
165 return result;
166 }
167
Hans Boehm682ff5e2015-03-09 14:40:25 -0700168 public BoundedRational toRational() {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700169 String whole = mWhole;
170 if (whole.isEmpty()) whole = "0";
171 BigInteger num = new BigInteger(whole + mFraction);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700172 BigInteger den = BigInteger.TEN.pow(mFraction.length());
173 return new BoundedRational(num, den);
174 }
175
Hans Boehm84614952014-11-25 18:46:17 -0800176 @Override
177 String toString(Context context) {
178 return toString();
179 }
180
181 @Override
182 TokenKind kind() { return TokenKind.CONSTANT; }
183
184 // Override clone to make it public
185 @Override
186 public Object clone() {
187 Constant res = new Constant();
188 res.mWhole = mWhole;
189 res.mFraction = mFraction;
190 res.mSawDecimal = mSawDecimal;
191 return res;
192 }
193 }
194
195 // Hash maps used to detect duplicate subexpressions when
196 // we write out CalculatorExprs and read them back in.
197 private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
198 new ThreadLocal<IdentityHashMap<CR,Integer>>();
199 // Maps expressions to indices on output
200 private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
201 new ThreadLocal<HashMap<Integer,PreEval>>();
202 // Maps expressions to indices on output
203 private static final ThreadLocal<Integer> exprIndex =
204 new ThreadLocal<Integer>();
205
206 static void initExprOutput() {
207 outMap.set(new IdentityHashMap<CR,Integer>());
208 exprIndex.set(Integer.valueOf(0));
209 }
210
211 static void initExprInput() {
212 inMap.set(new HashMap<Integer,PreEval>());
213 }
214
215 // We treat previously evaluated subexpressions as tokens
216 // These are inserted when either:
217 // - We continue an expression after evaluating some of it.
218 // - TODO: When we copy/paste expressions.
219 // The representation includes three different representations
220 // of the expression:
221 // 1) The CR value for use in computation.
222 // 2) The integer value for use in the computations,
223 // if the expression evaluates to an integer.
224 // 3a) The corresponding CalculatorExpr, together with
225 // 3b) The context (currently just deg/rad mode) used to evaluate
226 // the expression.
227 // 4) A short string representation that is used to
228 // Display the expression.
229 //
230 // (3) is present only so that we can persist the object.
231 // (4) is stored explicitly to avoid waiting for recomputation in the UI
232 // thread.
233 private static class PreEval extends Token {
234 final CR mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700235 final BoundedRational mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800236 private final CalculatorExpr mExpr;
237 private final EvalContext mContext;
238 private final String mShortRep;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700239 PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr, EvalContext ec,
Hans Boehm84614952014-11-25 18:46:17 -0800240 String shortRep) {
241 mValue = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700242 mRatValue = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800243 mExpr = expr;
244 mContext = ec;
245 mShortRep = shortRep;
246 }
247 // In writing out PreEvals, we are careful to avoid writing
248 // out duplicates. We assume that two expressions are
249 // duplicates if they have the same mVal. This avoids a
250 // potential exponential blow up in certain off cases and
251 // redundant evaluation after reading them back in.
252 // The parameter hash map maps expressions we've seen
253 // before to their index.
254 @Override
255 void write(DataOutput out) throws IOException {
256 out.writeByte(TokenKind.PRE_EVAL.ordinal());
257 Integer index = outMap.get().get(mValue);
258 if (index == null) {
259 int nextIndex = exprIndex.get() + 1;
260 exprIndex.set(nextIndex);
261 outMap.get().put(mValue, nextIndex);
262 out.writeInt(nextIndex);
263 mExpr.write(out);
264 mContext.write(out);
265 out.writeUTF(mShortRep);
266 } else {
267 // Just write out the index
268 out.writeInt(index);
269 }
270 }
271 PreEval(DataInput in) throws IOException {
272 int index = in.readInt();
273 PreEval prev = inMap.get().get(index);
274 if (prev == null) {
275 mExpr = new CalculatorExpr(in);
276 mContext = new EvalContext(in);
277 // Recompute other fields
278 // We currently do this in the UI thread, but we
279 // only create PreEval expressions that were
280 // previously successfully evaluated, and thus
281 // don't diverge. We also only evaluate to a
282 // constructive real, which involves substantial
283 // work only in fairly contrived circumstances.
284 // TODO: Deal better with slow evaluations.
285 EvalRet res = mExpr.evalExpr(0,mContext);
286 mValue = res.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700287 mRatValue = res.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800288 mShortRep = in.readUTF();
289 inMap.get().put(index, this);
290 } else {
291 mValue = prev.mValue;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700292 mRatValue = prev.mRatValue;
Hans Boehm84614952014-11-25 18:46:17 -0800293 mExpr = prev.mExpr;
294 mContext = prev.mContext;
295 mShortRep = prev.mShortRep;
296 }
297 }
298 @Override
299 String toString(Context context) {
300 return mShortRep;
301 }
302 @Override
303 TokenKind kind() { return TokenKind.PRE_EVAL; }
304 }
305
306 static Token newToken(DataInput in) throws IOException {
307 TokenKind kind = tokenKindValues[in.readByte()];
308 switch(kind) {
309 case CONSTANT:
310 return new Constant(in);
311 case OPERATOR:
312 return new Operator(in);
313 case PRE_EVAL:
314 return new PreEval(in);
315 default: throw new IOException("Bad save file format");
316 }
317 }
318
319 CalculatorExpr() {
320 mExpr = new ArrayList<Token>();
321 }
322
323 private CalculatorExpr(ArrayList<Token> expr) {
324 mExpr = expr;
325 }
326
327 CalculatorExpr(DataInput in) throws IOException {
328 mExpr = new ArrayList<Token>();
329 int size = in.readInt();
330 for (int i = 0; i < size; ++i) {
331 mExpr.add(newToken(in));
332 }
333 }
334
335 void write(DataOutput out) throws IOException {
336 int size = mExpr.size();
337 out.writeInt(size);
338 for (int i = 0; i < size; ++i) {
339 mExpr.get(i).write(out);
340 }
341 }
342
343 private boolean hasTrailingBinary() {
344 int s = mExpr.size();
345 if (s == 0) return false;
346 Token t = mExpr.get(s-1);
347 if (!(t instanceof Operator)) return false;
348 Operator o = (Operator)t;
349 return (KeyMaps.isBinary(o.mId));
350 }
351
352 // Append press of button with given id to expression.
353 // Returns false and does nothing if this would clearly
354 // result in a syntax error.
355 boolean add(int id) {
356 int s = mExpr.size();
357 int d = KeyMaps.digVal(id);
358 boolean binary = KeyMaps.isBinary(id);
359 if (s == 0 && binary && id != R.id.op_sub) return false;
360 if (binary && hasTrailingBinary()
361 && (id != R.id.op_sub || isOperator(s-1, R.id.op_sub))) {
362 return false;
363 }
364 boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
365 if (isConstPiece) {
366 if (s == 0) {
367 mExpr.add(new Constant());
368 s++;
369 } else {
370 Token last = mExpr.get(s-1);
371 if(!(last instanceof Constant)) {
372 if (!(last instanceof Operator)) {
373 return false;
374 }
375 int lastOp = ((Operator)last).mId;
376 if (lastOp == R.id.const_e || lastOp == R.id.const_pi
377 || lastOp == R.id.op_fact
378 || lastOp == R.id.rparen) {
379 // Constant cannot possibly follow; reject immediately
380 return false;
381 }
382 mExpr.add(new Constant());
383 s++;
384 }
385 }
386 return ((Constant)(mExpr.get(s-1))).add(id);
387 } else {
388 mExpr.add(new Operator(id));
389 return true;
390 }
391 }
392
393 // Append the contents of the argument expression.
394 // It is assumed that the argument expression will not change,
395 // and thus its pieces can be reused directly.
396 // TODO: We probably only need this for expressions consisting of
397 // a single PreEval "token", and may want to check that.
398 void append(CalculatorExpr expr2) {
399 int s2 = expr2.mExpr.size();
400 for (int i = 0; i < s2; ++i) {
401 mExpr.add(expr2.mExpr.get(i));
402 }
403 }
404
405 // Undo the last key addition, if any.
406 void delete() {
407 int s = mExpr.size();
408 if (s == 0) return;
409 Token last = mExpr.get(s-1);
410 if (last instanceof Constant) {
411 Constant c = (Constant)last;
412 c.delete();
413 if (!c.isEmpty()) return;
414 }
415 mExpr.remove(s-1);
416 }
417
418 void clear() {
419 mExpr.clear();
420 }
421
422 boolean isEmpty() {
423 return mExpr.isEmpty();
424 }
425
426 // Returns a logical deep copy of the CalculatorExpr.
427 // Operator and PreEval tokens are immutable, and thus
428 // aren't really copied.
429 public Object clone() {
430 CalculatorExpr res = new CalculatorExpr();
431 for (Token t: mExpr) {
432 if (t instanceof Constant) {
433 res.mExpr.add((Token)(((Constant)t).clone()));
434 } else {
435 res.mExpr.add(t);
436 }
437 }
438 return res;
439 }
440
441 // Am I just a constant?
442 boolean isConstant() {
443 if (mExpr.size() != 1) return false;
444 return mExpr.get(0) instanceof Constant;
445 }
446
447 // Return a new expression consisting of a single PreEval token
448 // representing the current expression.
449 // The caller supplies the value, degree mode, and short
450 // string representation, which must have been previously computed.
451 // Thus this is guaranteed to terminate reasonably quickly.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700452 CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800453 boolean dm, String sr) {
454 CalculatorExpr result = new CalculatorExpr();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700455 Token t = new PreEval(val, ratVal,
Hans Boehm84614952014-11-25 18:46:17 -0800456 new CalculatorExpr(
457 (ArrayList<Token>)mExpr.clone()),
458 new EvalContext(dm), sr);
459 result.mExpr.add(t);
460 return result;
461 }
462
463 // Internal evaluation functions return an EvalRet triple.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700464 // We compute rational (BoundedRational) results when possible, both as
Hans Boehm84614952014-11-25 18:46:17 -0800465 // a performance optimization, and to detect errors exactly when we can.
466 private class EvalRet {
467 int mPos; // Next position (expression index) to be parsed
468 final CR mVal; // Constructive Real result of evaluating subexpression
Hans Boehm682ff5e2015-03-09 14:40:25 -0700469 final BoundedRational mRatVal; // Exact Rational value or null if
470 // irrational or hard to compute.
471 EvalRet(int p, CR v, BoundedRational r) {
Hans Boehm84614952014-11-25 18:46:17 -0800472 mPos = p;
473 mVal = v;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700474 mRatVal = r;
Hans Boehm84614952014-11-25 18:46:17 -0800475 }
476 }
477
478 // And take a context argument:
479 private static class EvalContext {
480 // Memory register contents are not included here,
481 // since we now make that an explicit part of the expression
482 // If we add any other kinds of evaluation modes, they go here.
483 boolean mDegreeMode;
484 EvalContext(boolean degreeMode) {
485 mDegreeMode = degreeMode;
486 }
487 EvalContext(DataInput in) throws IOException {
488 mDegreeMode = in.readBoolean();
489 }
490 void write(DataOutput out) throws IOException {
491 out.writeBoolean(mDegreeMode);
492 }
493 }
494
495 private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
496
497 private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
498
499 private CR toRadians(CR x, EvalContext ec) {
500 if (ec.mDegreeMode) {
501 return x.multiply(RADIANS_PER_DEGREE);
502 } else {
503 return x;
504 }
505 }
506
507 private CR fromRadians(CR x, EvalContext ec) {
508 if (ec.mDegreeMode) {
509 return x.multiply(DEGREES_PER_RADIAN);
510 } else {
511 return x;
512 }
513 }
514
515 // The following methods can all throw IndexOutOfBoundsException
516 // in the event of a syntax error. We expect that to be caught in
517 // eval below.
518
519 private boolean isOperator(int i, int op) {
520 if (i >= mExpr.size()) return false;
521 Token t = mExpr.get(i);
522 if (!(t instanceof Operator)) return false;
523 return ((Operator)(t)).mId == op;
524 }
525
526 static class SyntaxError extends Error {
527 public SyntaxError() {
528 super();
529 }
530 public SyntaxError(String s) {
531 super(s);
532 }
533 }
534
535 // The following functions all evaluate some kind of expression
536 // starting at position i in mExpr in a specified evaluation context.
537 // They return both the expression value (as constructive real and,
538 // if applicable, as BigInteger) and the position of the next token
539 // that was not used as part of the evaluation.
540 private EvalRet evalUnary(int i, EvalContext ec)
541 throws ArithmeticException {
542 Token t = mExpr.get(i);
543 CR value;
544 if (t instanceof Constant) {
545 Constant c = (Constant)t;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700546 value = CR.valueOf(c.toNiceString(),10);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700547 return new EvalRet(i+1, value, c.toRational());
Hans Boehm84614952014-11-25 18:46:17 -0800548 }
549 if (t instanceof PreEval) {
550 PreEval p = (PreEval)t;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700551 return new EvalRet(i+1, p.mValue, p.mRatValue);
Hans Boehm84614952014-11-25 18:46:17 -0800552 }
553 EvalRet argVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700554 BoundedRational ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800555 switch(((Operator)(t)).mId) {
556 case R.id.const_pi:
557 return new EvalRet(i+1, CR.PI, null);
558 case R.id.const_e:
559 return new EvalRet(i+1, CR.valueOf(1).exp(), null);
560 case R.id.op_sqrt:
561 // Seems to have highest precedence
562 // Does not add implicit paren
563 argVal = evalUnary(i+1, ec);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700564 ratVal = BoundedRational.sqrt(argVal.mRatVal);
565 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800566 return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
567 case R.id.lparen:
568 argVal = evalExpr(i+1, ec);
569 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700570 return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800571 case R.id.fun_sin:
572 argVal = evalExpr(i+1, ec);
573 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700574 ratVal = ec.mDegreeMode? BoundedRational.degreeSin(argVal.mRatVal)
575 : BoundedRational.sin(argVal.mRatVal);
576 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800577 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700578 toRadians(argVal.mVal,ec).sin(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800579 case R.id.fun_cos:
580 argVal = evalExpr(i+1, ec);
581 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700582 ratVal = ec.mDegreeMode? BoundedRational.degreeCos(argVal.mRatVal)
583 : BoundedRational.cos(argVal.mRatVal);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700584 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800585 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700586 toRadians(argVal.mVal,ec).cos(), null);
Hans Boehm84614952014-11-25 18:46:17 -0800587 case R.id.fun_tan:
588 argVal = evalExpr(i+1, ec);
589 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700590 ratVal = ec.mDegreeMode? BoundedRational.degreeTan(argVal.mRatVal)
591 : BoundedRational.tan(argVal.mRatVal);
592 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800593 CR argCR = toRadians(argVal.mVal, ec);
594 return new EvalRet(argVal.mPos,
Hans Boehm682ff5e2015-03-09 14:40:25 -0700595 argCR.sin().divide(argCR.cos()), null);
Hans Boehm84614952014-11-25 18:46:17 -0800596 case R.id.fun_ln:
597 argVal = evalExpr(i+1, ec);
598 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700599 ratVal = BoundedRational.ln(argVal.mRatVal);
600 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800601 return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
602 case R.id.fun_log:
603 argVal = evalExpr(i+1, ec);
604 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700605 ratVal = BoundedRational.log(argVal.mRatVal);
606 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800607 return new EvalRet(argVal.mPos,
608 argVal.mVal.ln().divide(CR.valueOf(10).ln()),
609 null);
610 case R.id.fun_arcsin:
611 argVal = evalExpr(i+1, ec);
612 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700613 ratVal = ec.mDegreeMode? BoundedRational.degreeAsin(argVal.mRatVal)
614 : BoundedRational.asin(argVal.mRatVal);
615 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800616 return new EvalRet(argVal.mPos,
617 fromRadians(UnaryCRFunction
618 .asinFunction.execute(argVal.mVal),ec),
619 null);
620 case R.id.fun_arccos:
621 argVal = evalExpr(i+1, ec);
622 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700623 ratVal = ec.mDegreeMode? BoundedRational.degreeAcos(argVal.mRatVal)
624 : BoundedRational.acos(argVal.mRatVal);
625 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800626 return new EvalRet(argVal.mPos,
627 fromRadians(UnaryCRFunction
628 .acosFunction.execute(argVal.mVal),ec),
629 null);
630 case R.id.fun_arctan:
631 argVal = evalExpr(i+1, ec);
632 if (isOperator(argVal.mPos, R.id.rparen)) argVal.mPos++;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700633 ratVal = ec.mDegreeMode? BoundedRational.degreeAtan(argVal.mRatVal)
634 : BoundedRational.atan(argVal.mRatVal);
635 if (ratVal != null) break;
Hans Boehm84614952014-11-25 18:46:17 -0800636 return new EvalRet(argVal.mPos,
637 fromRadians(UnaryCRFunction
638 .atanFunction.execute(argVal.mVal),ec),
639 null);
640 default:
641 throw new SyntaxError("Unrecognized token in expression");
642 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700643 // We have a rational value.
644 return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800645 }
646
647 // Compute an integral power of a constructive real.
648 // Unlike the "general" case using logarithms, this handles a negative
649 // base.
650 private static CR pow(CR base, BigInteger exp) {
651 if (exp.compareTo(BigInteger.ZERO) < 0) {
652 return pow(base, exp.negate()).inverse();
653 }
654 if (exp.equals(BigInteger.ONE)) return base;
655 if (exp.and(BigInteger.ONE).intValue() == 1) {
656 return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
657 }
658 if (exp.equals(BigInteger.ZERO)) {
659 return CR.valueOf(1);
660 }
661 CR tmp = pow(base, exp.shiftRight(1));
662 return tmp.multiply(tmp);
663 }
664
Hans Boehm682ff5e2015-03-09 14:40:25 -0700665 private static final int TEST_PREC = -100;
666 // Test for integer-ness to 100 bits past binary point.
667 private static final BigInteger MASK =
668 BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
669 private static boolean isApprInt(CR x) {
670 BigInteger appr = x.get_appr(TEST_PREC);
671 return appr.and(MASK).signum() == 0;
672 }
673
Hans Boehm84614952014-11-25 18:46:17 -0800674 private EvalRet evalFactorial(int i, EvalContext ec) {
675 EvalRet tmp = evalUnary(i, ec);
676 int cpos = tmp.mPos;
677 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700678 BoundedRational ratVal = tmp.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800679 while (isOperator(cpos, R.id.op_fact)) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700680 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800681 // Assume it was an integer, but we
682 // didn't figure it out.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700683 // KitKat may have used the Gamma function.
684 if (!isApprInt(cval)) {
685 throw new ArithmeticException("factorial(non-integer)");
686 }
687 ratVal = new BoundedRational(cval.BigIntegerValue());
Hans Boehm84614952014-11-25 18:46:17 -0800688 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700689 ratVal = BoundedRational.fact(ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800690 ++cpos;
691 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700692 if (ratVal != null) cval = ratVal.CRValue();
693 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800694 }
695
696 private EvalRet evalFactor(int i, EvalContext ec)
697 throws ArithmeticException {
698 final EvalRet result1 = evalFactorial(i, ec);
699 int cpos = result1.mPos; // current position
700 CR cval = result1.mVal; // value so far
Hans Boehm682ff5e2015-03-09 14:40:25 -0700701 BoundedRational ratVal = result1.mRatVal; // int value so far
Hans Boehm84614952014-11-25 18:46:17 -0800702 if (isOperator(cpos, R.id.op_pow)) {
703 final EvalRet exp = evalSignedFactor(cpos+1, ec);
704 cpos = exp.mPos;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700705 // Try completely rational evaluation first.
706 ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
707 if (ratVal != null) {
708 return new EvalRet(cpos, ratVal.CRValue(), ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800709 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700710 // Power with integer exponent is defined for negative base.
711 // Thus we handle that case separately.
712 // We punt if the exponent is an integer computed from irrational
713 // values. That wouldn't work reliably with floating point either.
714 BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
715 if (int_exp != null) {
716 cval = pow(cval, int_exp);
Hans Boehm84614952014-11-25 18:46:17 -0800717 } else {
Hans Boehm84614952014-11-25 18:46:17 -0800718 cval = cval.ln().multiply(exp.mVal).exp();
719 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700720 ratVal = null;
Hans Boehm84614952014-11-25 18:46:17 -0800721 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700722 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800723 }
724
725 private EvalRet evalSignedFactor(int i, EvalContext ec)
726 throws ArithmeticException {
727 final boolean negative = isOperator(i, R.id.op_sub);
728 int cpos = negative? i + 1 : i;
729 EvalRet tmp = evalFactor(cpos, ec);
730 cpos = tmp.mPos;
731 CR cval = negative? tmp.mVal.negate() : tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700732 BoundedRational ratVal = negative? BoundedRational.negate(tmp.mRatVal)
733 : tmp.mRatVal;
734 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800735 }
736
737 private boolean canStartFactor(int i) {
738 if (i >= mExpr.size()) return false;
739 Token t = mExpr.get(i);
740 if (!(t instanceof Operator)) return true;
741 int id = ((Operator)(t)).mId;
742 if (KeyMaps.isBinary(id)) return false;
743 switch (id) {
744 case R.id.op_fact:
745 case R.id.rparen:
746 return false;
747 default:
748 return true;
749 }
750 }
751
752 private EvalRet evalTerm(int i, EvalContext ec)
753 throws ArithmeticException {
754 EvalRet tmp = evalSignedFactor(i, ec);
755 boolean is_mul = false;
756 boolean is_div = false;
757 int cpos = tmp.mPos; // Current position in expression.
758 CR cval = tmp.mVal; // Current value.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700759 BoundedRational ratVal = tmp.mRatVal; // Current rational value.
Hans Boehm84614952014-11-25 18:46:17 -0800760 while ((is_mul = isOperator(cpos, R.id.op_mul))
761 || (is_div = isOperator(cpos, R.id.op_div))
762 || canStartFactor(cpos)) {
763 if (is_mul || is_div) ++cpos;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700764 tmp = evalSignedFactor(cpos, ec);
Hans Boehm84614952014-11-25 18:46:17 -0800765 if (is_div) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700766 ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
767 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800768 cval = cval.divide(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700769 } else {
770 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800771 }
772 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700773 ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
774 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800775 cval = cval.multiply(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700776 } else {
777 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800778 }
779 }
780 cpos = tmp.mPos;
781 is_mul = is_div = false;
782 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700783 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800784 }
785
786 private EvalRet evalExpr(int i, EvalContext ec) throws ArithmeticException {
787 EvalRet tmp = evalTerm(i, ec);
788 boolean is_plus;
789 int cpos = tmp.mPos;
790 CR cval = tmp.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700791 BoundedRational ratVal = tmp.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800792 while ((is_plus = isOperator(cpos, R.id.op_add))
793 || isOperator(cpos, R.id.op_sub)) {
794 tmp = evalTerm(cpos+1, ec);
795 if (is_plus) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700796 ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
797 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800798 cval = cval.add(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700799 } else {
800 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800801 }
802 } else {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700803 ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
804 if (ratVal == null) {
Hans Boehm84614952014-11-25 18:46:17 -0800805 cval = cval.subtract(tmp.mVal);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700806 } else {
807 cval = ratVal.CRValue();
Hans Boehm84614952014-11-25 18:46:17 -0800808 }
809 }
810 cpos = tmp.mPos;
811 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700812 return new EvalRet(cpos, cval, ratVal);
Hans Boehm84614952014-11-25 18:46:17 -0800813 }
814
815 // Externally visible evaluation result.
816 public class EvalResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700817 EvalResult (CR val, BoundedRational ratVal) {
Hans Boehm84614952014-11-25 18:46:17 -0800818 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700819 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800820 }
821 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700822 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800823 }
824
825 // Evaluate the entire expression, returning null in the event
826 // of an error.
827 // Not called from the UI thread, but should not be called
828 // concurrently with modifications to the expression.
829 EvalResult eval(boolean degreeMode) throws SyntaxError,
830 ArithmeticException, PrecisionOverflowError
831 {
832 try {
833 EvalContext ec = new EvalContext(degreeMode);
834 EvalRet res = evalExpr(0, ec);
835 if (res.mPos != mExpr.size()) return null;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700836 return new EvalResult(res.mVal, res.mRatVal);
Hans Boehm84614952014-11-25 18:46:17 -0800837 } catch (IndexOutOfBoundsException e) {
838 throw new SyntaxError("Unexpected expression end");
839 }
840 }
841
842 // Produce a string representation of the expression itself
843 String toString(Context context) {
844 StringBuilder sb = new StringBuilder();
845 for (Token t: mExpr) {
846 sb.append(t.toString(context));
847 }
848 return sb.toString();
849 }
850}