blob: afde752469e0de3b63c6d83592ecce9263ebcd75 [file] [log] [blame]
Hans Boehm84614952014-11-25 18:46:17 -08001/*
2 * Copyright (C) 2015 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
Hans Boehm84614952014-11-25 18:46:17 -080017//
18// This implements the calculator evaluation logic.
19// An evaluation is started with a call to evaluateAndShowResult().
20// This starts an asynchronous computation, which requests display
21// of the initial result, when available. When initial evaluation is
22// complete, it calls the calculator onEvaluate() method.
23// This occurs in a separate event, and may happen quite a bit
24// later. Once a result has been computed, and before the underlying
25// expression is modified, the getString method may be used to produce
26// Strings that represent approximations to various precisions.
27//
28// Actual expressions being evaluated are represented as CalculatorExprs,
29// which are just slightly preprocessed sequences of keypresses.
30//
31// The Evaluator owns the expression being edited and associated
32// state needed for evaluating it. It provides functionality for
33// saving and restoring this state. However the current
34// CalculatorExpr is exposed to the client, and may be directly modified
35// after cancelling any in-progress computations by invoking the
36// cancelAll() method.
37//
38// When evaluation is requested by the user, we invoke the eval
39// method on the CalculatorExpr from a background AsyncTask.
40// A subsequent getString() callback returns immediately, though it may
41// return a result containing placeholder '?' characters.
42// In that case we start a background task, which invokes the
43// onReevaluate() callback when it completes.
44// In both cases, the background task
45// computes the appropriate result digits by evaluating
46// the constructive real (CR) returned by CalculatorExpr.eval()
47// to the required precision.
48//
49// We cache the best approximation we have already computed.
50// We compute generously to allow for
51// some scrolling without recomputation and to minimize the chance of
52// digits flipping from "0000" to "9999". The best known
53// result approximation is maintained as a string by mCache (and
54// in a different format by the CR representation of the result).
55// When we are in danger of not having digits to display in response
56// to further scrolling, we initiate a background computation to higher
57// precision. If we actually do fall behind, we display placeholder
Hans Boehma0e45f32015-05-30 13:20:35 -070058// characters, e.g. blanks, and schedule a display update when the computation
Hans Boehm84614952014-11-25 18:46:17 -080059// completes.
60// The code is designed to ensure that the error in the displayed
Hans Boehma0e45f32015-05-30 13:20:35 -070061// result (excluding any placeholder characters) is always strictly less than 1 in
Hans Boehm84614952014-11-25 18:46:17 -080062// the last displayed digit. Typically we actually display a prefix
63// of a result that has this property and additionally is computed to
64// a significantly higher precision. Thus we almost always round correctly
65// towards zero. (Fully correct rounding towards zero is not computable.)
Hans Boehmc023b732015-04-29 11:30:47 -070066//
67// Initial expression evaluation may time out. This may happen in the
68// case of domain errors such as division by zero, or for large computations.
69// We do not currently time out reevaluations to higher precision, since
70// the original evaluation prevcluded a domain error that could result
71// in non-termination. (We may discover that a presumed zero result is
72// actually slightly negative when re-evaluated; but that results in an
73// exception, which we can handle.) The user can abort either kind
74// of computation.
75//
76// We ensure that only one evaluation of either kind (AsyncReevaluator
77// or AsyncDisplayResult) is running at a time.
Hans Boehm84614952014-11-25 18:46:17 -080078
79package com.android.calculator2;
80
Hans Boehm84614952014-11-25 18:46:17 -080081import android.app.AlertDialog;
Justin Klaassen8b1efdb2015-06-22 15:10:53 -070082import android.content.Context;
Hans Boehm84614952014-11-25 18:46:17 -080083import android.content.DialogInterface;
Justin Klaassen8b1efdb2015-06-22 15:10:53 -070084import android.content.SharedPreferences;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070085import android.net.Uri;
86import android.os.AsyncTask;
Hans Boehm84614952014-11-25 18:46:17 -080087import android.os.Handler;
Justin Klaassen8b1efdb2015-06-22 15:10:53 -070088import android.preference.PreferenceManager;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070089import android.util.Log;
Hans Boehm84614952014-11-25 18:46:17 -080090
91import com.hp.creals.CR;
Hans Boehm84614952014-11-25 18:46:17 -080092
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070093import java.io.DataInput;
94import java.io.DataOutput;
95import java.io.IOException;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070096import java.math.BigInteger;
Justin Klaassene2711cb2015-05-28 11:13:17 -070097import java.text.DateFormat;
98import java.text.SimpleDateFormat;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070099import java.util.Date;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700100import java.util.Random;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700101import java.util.TimeZone;
102
Hans Boehm84614952014-11-25 18:46:17 -0800103class Evaluator {
Justin Klaassenecc69fd2015-05-07 10:30:57 -0700104
Justin Klaassen8b1efdb2015-06-22 15:10:53 -0700105 private static final String KEY_PREF_DEGREE_MODE = "degree_mode";
106
Hans Boehm84614952014-11-25 18:46:17 -0800107 private final Calculator mCalculator;
108 private final CalculatorResult mResult; // The result display View
109 private CalculatorExpr mExpr; // Current calculator expression
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700110 private CalculatorExpr mSaved; // Last saved expression.
111 // Either null or contains a single
112 // preevaluated node.
113 private String mSavedName; // A hopefully unique name associated
114 // with mSaved.
Hans Boehm84614952014-11-25 18:46:17 -0800115 // The following are valid only if an evaluation
116 // completed successfully.
117 private CR mVal; // value of mExpr as constructive real
Hans Boehm682ff5e2015-03-09 14:40:25 -0700118 private BoundedRational mRatVal; // value of mExpr as rational or null
Hans Boehm84614952014-11-25 18:46:17 -0800119 private int mLastDigs; // Last digit argument passed to getString()
120 // for this result, or the initial preferred
121 // precision.
122 private boolean mDegreeMode; // Currently in degree (not radian) mode
123 private final Handler mTimeoutHandler;
124
Hans Boehm84614952014-11-25 18:46:17 -0800125 static final BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
126
Hans Boehm08e8f322015-04-21 13:18:38 -0700127 private static final int EXTRA_DIGITS = 20;
Hans Boehm84614952014-11-25 18:46:17 -0800128 // Extra computed digits to minimize probably we will have
129 // to change our minds about digits we already displayed.
130 // (The correct digits are technically not computable using our
131 // representation: An off by one error in the last digits
132 // can affect earlier ones, even though the display is
133 // always within one in the lsd. This is only visible
134 // for results that end in EXTRA_DIGITS 9s or 0s, but are
135 // not integers.)
136 // We do use these extra digits to display while we are
137 // computing the correct answer. Thus they may be
138 // temporarily visible.
Hans Boehme4579122015-05-26 17:52:32 -0700139 private static final int EXTRA_DIVISOR = 5;
140 // We add the length of the previous result divided by
141 // EXTRA_DIVISOR to try to recover recompute latency when
142 // scrolling through a long result.
143 private static final int PRECOMPUTE_DIGITS = 30;
144 private static final int PRECOMPUTE_DIVISOR = 5;
145 // When we have to reevaluate, we compute an extra
146 // PRECOMPUTE_DIGITS
147 // + <current_result_length>/PRECOMPUTE_DIVISOR digits.
148 // The last term is dropped if prec < 0.
Hans Boehm84614952014-11-25 18:46:17 -0800149
150 // We cache the result as a string to accelerate scrolling.
151 // The cache is filled in by the UI thread, but this may
152 // happen asynchronously, much later than the request.
153 private String mCache; // Current best known result, which includes
154 private int mCacheDigs = 0; // mCacheDigs digits to the right of the
155 // decimal point. Always positive.
156 // mCache is valid when non-null
157 // unless the expression has been
158 // changed since the last evaluation call.
159 private int mCacheDigsReq; // Number of digits that have been
160 // requested. Only touched by UI
161 // thread.
Hans Boehm08e8f322015-04-21 13:18:38 -0700162 public static final int INVALID_MSD = Integer.MAX_VALUE;
Hans Boehm84614952014-11-25 18:46:17 -0800163 private int mMsd = INVALID_MSD; // Position of most significant digit
164 // in current cached result, if determined.
165 // This is just the index in mCache
166 // holding the msd.
Hans Boehm50ed3202015-06-09 14:35:49 -0700167 private static final int INIT_PREC = 50;
168 // Initial evaluation precision. Enough to guarantee
169 // that we can compute the short representation, and that
170 // we rarely have to evaluate nonzero results to
171 // MAX_MSD_PREC. It also helps if this is at least
172 // EXTRA_DIGITS + display width, so that we don't
173 // immediately need a second evaluation.
174 private static final int MAX_MSD_PREC = 320;
Hans Boehm84614952014-11-25 18:46:17 -0800175 // The largest number of digits to the right
176 // of the decimal point to which we will
177 // evaluate to compute proper scientific
178 // notation for values close to zero.
Hans Boehm50ed3202015-06-09 14:35:49 -0700179 // Chosen to ensure that we always to better than
180 // IEEE double precision at identifying nonzeros.
181 private static final int EXP_COST = 3;
182 // If we can replace an exponent by this many leading zeroes,
183 // we do so. Also used in estimating exponent size for
184 // truncating short representation.
Hans Boehm84614952014-11-25 18:46:17 -0800185
186 private AsyncReevaluator mCurrentReevaluator;
187 // The one and only un-cancelled and currently running reevaluator.
188 // Touched only by UI thread.
189
190 private AsyncDisplayResult mEvaluator;
191 // Currently running expression evaluator, if any.
192
Hans Boehmc023b732015-04-29 11:30:47 -0700193 private boolean mChangedValue;
Hans Boehm187d3e92015-06-09 18:04:26 -0700194 // The expression may have changed since the last evaluation in ways that would
195 // affect its value.
Hans Boehmc023b732015-04-29 11:30:47 -0700196
Justin Klaassen8b1efdb2015-06-22 15:10:53 -0700197 private SharedPreferences mSharedPrefs;
198
Hans Boehm84614952014-11-25 18:46:17 -0800199 Evaluator(Calculator calculator,
200 CalculatorResult resultDisplay) {
201 mCalculator = calculator;
202 mResult = resultDisplay;
203 mExpr = new CalculatorExpr();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700204 mSaved = new CalculatorExpr();
205 mSavedName = "none";
Hans Boehm84614952014-11-25 18:46:17 -0800206 mTimeoutHandler = new Handler();
Justin Klaassen8b1efdb2015-06-22 15:10:53 -0700207
208 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(calculator);
209 mDegreeMode = mSharedPrefs.getBoolean(KEY_PREF_DEGREE_MODE, false);
Hans Boehm84614952014-11-25 18:46:17 -0800210 }
211
212 // Result of asynchronous reevaluation
213 class ReevalResult {
214 ReevalResult(String s, int p) {
215 mNewCache = s;
216 mNewCacheDigs = p;
217 }
218 final String mNewCache;
219 final int mNewCacheDigs;
220 }
221
222 // Compute new cache contents accurate to prec digits to the right
223 // of the decimal point. Ensure that redisplay() is called after
224 // doing so. If the evaluation fails for reasons other than a
225 // timeout, ensure that DisplayError() is called.
226 class AsyncReevaluator extends AsyncTask<Integer, Void, ReevalResult> {
227 @Override
228 protected ReevalResult doInBackground(Integer... prec) {
229 try {
230 int eval_prec = prec[0].intValue();
231 return new ReevalResult(mVal.toString(eval_prec), eval_prec);
232 } catch(ArithmeticException e) {
233 return null;
Hans Boehm19e93c92015-06-19 18:31:28 -0700234 } catch(CR.PrecisionOverflowException e) {
Hans Boehm84614952014-11-25 18:46:17 -0800235 return null;
Hans Boehm19e93c92015-06-19 18:31:28 -0700236 } catch(CR.AbortedException e) {
Hans Boehm84614952014-11-25 18:46:17 -0800237 // Should only happen if the task was cancelled,
238 // in which case we don't look at the result.
239 return null;
240 }
241 }
242 @Override
243 protected void onPostExecute(ReevalResult result) {
244 if (result == null) {
245 // This should only be possible in the extremely rare
Hans Boehmc023b732015-04-29 11:30:47 -0700246 // case of encountering a domain error while reevaluating
247 // or in case of a precision overflow. We don't know of
248 // a way to get the latter with a plausible amount of
249 // user input.
Hans Boehm84614952014-11-25 18:46:17 -0800250 mCalculator.onError(R.string.error_nan);
251 } else {
252 if (result.mNewCacheDigs < mCacheDigs) {
Hans Boehm19e93c92015-06-19 18:31:28 -0700253 throw new AssertionError("Unexpected onPostExecute timing");
Hans Boehm84614952014-11-25 18:46:17 -0800254 }
255 mCache = result.mNewCache;
256 mCacheDigs = result.mNewCacheDigs;
257 mCalculator.onReevaluate();
258 }
259 mCurrentReevaluator = null;
260 }
261 // On cancellation we do nothing; invoker should have
262 // left no trace of us.
263 }
264
265 // Result of initial asynchronous computation
266 private static class InitialResult {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700267 InitialResult(CR val, BoundedRational ratVal, String s, int p, int idp) {
Hans Boehm84614952014-11-25 18:46:17 -0800268 mErrorResourceId = Calculator.INVALID_RES_ID;
269 mVal = val;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700270 mRatVal = ratVal;
Hans Boehm84614952014-11-25 18:46:17 -0800271 mNewCache = s;
272 mNewCacheDigs = p;
273 mInitDisplayPrec = idp;
274 }
275 InitialResult(int errorResourceId) {
276 mErrorResourceId = errorResourceId;
277 mVal = CR.valueOf(0);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700278 mRatVal = BoundedRational.ZERO;
Hans Boehm84614952014-11-25 18:46:17 -0800279 mNewCache = "BAD";
280 mNewCacheDigs = 0;
281 mInitDisplayPrec = 0;
282 }
283 boolean isError() {
284 return mErrorResourceId != Calculator.INVALID_RES_ID;
285 }
286 final int mErrorResourceId;
287 final CR mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700288 final BoundedRational mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800289 final String mNewCache; // Null iff it can't be computed.
290 final int mNewCacheDigs;
291 final int mInitDisplayPrec;
292 }
293
294 private void displayCancelledMessage() {
Hans Boehmc023b732015-04-29 11:30:47 -0700295 new AlertDialog.Builder(mCalculator)
296 .setMessage(R.string.cancelled)
Justin Klaassencc1e8e22015-06-04 18:25:46 -0700297 .setPositiveButton(R.string.dismiss,
Hans Boehmc023b732015-04-29 11:30:47 -0700298 new DialogInterface.OnClickListener() {
299 public void onClick(DialogInterface d, int which) { }
300 })
301 .create()
302 .show();
Hans Boehm84614952014-11-25 18:46:17 -0800303 }
304
305 private final long MAX_TIMEOUT = 60000;
306 // Milliseconds.
307 // Longer is unlikely to help unless
308 // we get more heap space.
309 private long mTimeout = 2000; // Timeout for requested evaluations,
310 // in milliseconds.
311 // This is currently not saved and restored
Hans Boehmc023b732015-04-29 11:30:47 -0700312 // with the state; we reset
313 // the timeout when the
Hans Boehm84614952014-11-25 18:46:17 -0800314 // calculator is restarted.
315 // We'll call that a feature; others
316 // might argue it's a bug.
Hans Boehmc023b732015-04-29 11:30:47 -0700317 private final long mQuickTimeout = 1500;
Hans Boehm84614952014-11-25 18:46:17 -0800318 // Timeout for unrequested, speculative
319 // evaluations, in milliseconds.
Hans Boehmc023b732015-04-29 11:30:47 -0700320 // Could be shorter with a faster asin()
321 // implementation.
Hans Boehm84614952014-11-25 18:46:17 -0800322
323 private void displayTimeoutMessage() {
Justin Klaassencc1e8e22015-06-04 18:25:46 -0700324 final AlertDialog.Builder builder = new AlertDialog.Builder(mCalculator)
325 .setMessage(R.string.timeout)
326 .setNegativeButton(R.string.dismiss, null /* listener */);
Hans Boehmc023b732015-04-29 11:30:47 -0700327 if (mTimeout != MAX_TIMEOUT) {
Justin Klaassencc1e8e22015-06-04 18:25:46 -0700328 builder.setPositiveButton(R.string.ok_remove_timeout,
329 new DialogInterface.OnClickListener() {
330 public void onClick(DialogInterface d, int which) {
331 mTimeout = MAX_TIMEOUT;
332 }
333 });
Hans Boehmc023b732015-04-29 11:30:47 -0700334 }
Justin Klaassencc1e8e22015-06-04 18:25:46 -0700335 builder.show();
Hans Boehm84614952014-11-25 18:46:17 -0800336 }
337
Hans Boehm84614952014-11-25 18:46:17 -0800338 // Compute initial cache contents and result when we're good and ready.
339 // We leave the expression display up, with scrolling
340 // disabled, until this computation completes.
341 // Can result in an error display if something goes wrong.
342 // By default we set a timeout to catch runaway computations.
343 class AsyncDisplayResult extends AsyncTask<Void, Void, InitialResult> {
344 private boolean mDm; // degrees
345 private boolean mRequired; // Result was requested by user.
Hans Boehmc1ea0912015-06-19 15:05:07 -0700346 private boolean mQuiet; // Suppress cancellation message.
Hans Boehmc023b732015-04-29 11:30:47 -0700347 private Runnable mTimeoutRunnable = null;
Hans Boehm84614952014-11-25 18:46:17 -0800348 AsyncDisplayResult(boolean dm, boolean required) {
349 mDm = dm;
350 mRequired = required;
Hans Boehmc1ea0912015-06-19 15:05:07 -0700351 mQuiet = !required;
Hans Boehm84614952014-11-25 18:46:17 -0800352 }
Hans Boehmc023b732015-04-29 11:30:47 -0700353 private void handleTimeOut() {
354 boolean running = (getStatus() != AsyncTask.Status.FINISHED);
355 if (running && cancel(true)) {
356 mEvaluator = null;
357 // Replace mExpr with clone to avoid races if task
358 // still runs for a while.
359 mExpr = (CalculatorExpr)mExpr.clone();
Hans Boehmc023b732015-04-29 11:30:47 -0700360 if (mRequired) {
Hans Boehmc1ea0912015-06-19 15:05:07 -0700361 suppressCancelMessage();
Hans Boehmc023b732015-04-29 11:30:47 -0700362 displayTimeoutMessage();
363 }
364 }
365 }
Hans Boehmc1ea0912015-06-19 15:05:07 -0700366 private void suppressCancelMessage() {
367 mQuiet = true;
368 }
Hans Boehm84614952014-11-25 18:46:17 -0800369 @Override
370 protected void onPreExecute() {
Hans Boehm08e8f322015-04-21 13:18:38 -0700371 long timeout = mRequired ? mTimeout : mQuickTimeout;
Hans Boehm84614952014-11-25 18:46:17 -0800372 if (timeout != 0) {
Hans Boehmc023b732015-04-29 11:30:47 -0700373 mTimeoutRunnable = new Runnable() {
374 @Override
375 public void run() {
376 handleTimeOut();
377 }
378 };
379 mTimeoutHandler.postDelayed(mTimeoutRunnable, timeout);
Hans Boehm84614952014-11-25 18:46:17 -0800380 }
381 }
382 @Override
383 protected InitialResult doInBackground(Void... nothing) {
384 try {
Hans Boehmc023b732015-04-29 11:30:47 -0700385 CalculatorExpr.EvalResult res = mExpr.eval(mDm, mRequired);
Hans Boehm50ed3202015-06-09 14:35:49 -0700386 int prec = INIT_PREC;
Hans Boehm84614952014-11-25 18:46:17 -0800387 String initCache = res.mVal.toString(prec);
388 int msd = getMsdPos(initCache);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700389 if (BoundedRational.asBigInteger(res.mRatVal) == null
390 && msd == INVALID_MSD) {
Hans Boehm84614952014-11-25 18:46:17 -0800391 prec = MAX_MSD_PREC;
392 initCache = res.mVal.toString(prec);
393 msd = getMsdPos(initCache);
394 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700395 int lsd = getLsd(res.mRatVal, initCache, initCache.indexOf('.'));
396 int initDisplayPrec = getPreferredPrec(initCache, msd, lsd);
Hans Boehm84614952014-11-25 18:46:17 -0800397 int newPrec = initDisplayPrec + EXTRA_DIGITS;
398 if (newPrec > prec) {
399 prec = newPrec;
400 initCache = res.mVal.toString(prec);
401 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700402 return new InitialResult(res.mVal, res.mRatVal,
Hans Boehm84614952014-11-25 18:46:17 -0800403 initCache, prec, initDisplayPrec);
Hans Boehmc023b732015-04-29 11:30:47 -0700404 } catch (CalculatorExpr.SyntaxException e) {
Hans Boehm84614952014-11-25 18:46:17 -0800405 return new InitialResult(R.string.error_syntax);
Hans Boehmfbcef702015-04-27 18:07:47 -0700406 } catch (BoundedRational.ZeroDivisionException e) {
407 // Division by zero caught by BoundedRational;
408 // the easy and more common case.
409 return new InitialResult(R.string.error_zero_divide);
Hans Boehm84614952014-11-25 18:46:17 -0800410 } catch(ArithmeticException e) {
411 return new InitialResult(R.string.error_nan);
Hans Boehm19e93c92015-06-19 18:31:28 -0700412 } catch(CR.PrecisionOverflowException e) {
Hans Boehm84614952014-11-25 18:46:17 -0800413 // Extremely unlikely unless we're actually dividing by
414 // zero or the like.
415 return new InitialResult(R.string.error_overflow);
Hans Boehm19e93c92015-06-19 18:31:28 -0700416 } catch(CR.AbortedException e) {
Hans Boehm84614952014-11-25 18:46:17 -0800417 return new InitialResult(R.string.error_aborted);
418 }
419 }
420 @Override
421 protected void onPostExecute(InitialResult result) {
422 mEvaluator = null;
423 mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
424 if (result.isError()) {
425 mCalculator.onError(result.mErrorResourceId);
426 return;
427 }
428 mVal = result.mVal;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700429 mRatVal = result.mRatVal;
Hans Boehm84614952014-11-25 18:46:17 -0800430 mCache = result.mNewCache;
431 mCacheDigs = result.mNewCacheDigs;
432 mLastDigs = result.mInitDisplayPrec;
433 int dotPos = mCache.indexOf('.');
434 String truncatedWholePart = mCache.substring(0, dotPos);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700435 // Recheck display precision; it may change, since
436 // display dimensions may have been unknow the first time.
437 // In that case the initial evaluation precision should have
438 // been conservative.
439 // TODO: Could optimize by remembering display size and
440 // checking for change.
441 int init_prec = result.mInitDisplayPrec;
442 int msd = getMsdPos(mCache);
Hans Boehma0e45f32015-05-30 13:20:35 -0700443 int leastDigPos = getLsd(mRatVal, mCache, dotPos);
Hans Boehm61568a12015-05-18 18:25:41 -0700444 int new_init_prec = getPreferredPrec(mCache, msd, leastDigPos);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700445 if (new_init_prec < init_prec) {
446 init_prec = new_init_prec;
447 } else {
448 // They should be equal. But nothing horrible should
449 // happen if they're not. e.g. because
450 // CalculatorResult.MAX_WIDTH was too small.
451 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700452 mCalculator.onEvaluate(init_prec, msd, leastDigPos, truncatedWholePart);
Hans Boehm84614952014-11-25 18:46:17 -0800453 }
454 @Override
455 protected void onCancelled(InitialResult result) {
Hans Boehmc1ea0912015-06-19 15:05:07 -0700456 if (mRequired && !mQuiet) {
Hans Boehmc023b732015-04-29 11:30:47 -0700457 displayCancelledMessage();
458 } // Otherwise timeout processing displayed message.
Hans Boehm84614952014-11-25 18:46:17 -0800459 mCalculator.onCancelled();
460 // Just drop the evaluation; Leave expression displayed.
461 return;
462 }
463 }
464
465
466 // Start an evaluation to prec, and ensure that the
467 // display is redrawn when it completes.
468 private void ensureCachePrec(int prec) {
469 if (mCache != null && mCacheDigs >= prec
470 || mCacheDigsReq >= prec) return;
471 if (mCurrentReevaluator != null) {
472 // Ensure we only have one evaluation running at a time.
473 mCurrentReevaluator.cancel(true);
474 mCurrentReevaluator = null;
475 }
476 mCurrentReevaluator = new AsyncReevaluator();
477 mCacheDigsReq = prec + PRECOMPUTE_DIGITS;
Hans Boehme4579122015-05-26 17:52:32 -0700478 if (mCache != null) {
479 mCacheDigsReq += mCacheDigsReq / PRECOMPUTE_DIVISOR;
480 }
Hans Boehm84614952014-11-25 18:46:17 -0800481 mCurrentReevaluator.execute(mCacheDigsReq);
482 }
483
Hans Boehma0e45f32015-05-30 13:20:35 -0700484 /**
485 * Return the rightmost nonzero digit position, if any.
486 * @param ratVal Rational value of result or null.
487 * @param cache Current cached decimal string representation of result.
488 * @param decPos Index of decimal point in cache.
489 * @result Position of rightmost nonzero digit relative to decimal point.
490 * Integer.MIN_VALUE if ratVal is zero. Integer.MAX_VALUE if there is no lsd,
491 * or we cannot determine it.
492 */
493 int getLsd(BoundedRational ratVal, String cache, int decPos) {
494 if (ratVal != null && ratVal.signum() == 0) return Integer.MIN_VALUE;
495 int result = BoundedRational.digitsRequired(ratVal);
496 if (result == 0) {
497 int i;
498 for (i = -1; decPos + i > 0 && cache.charAt(decPos + i) == '0'; --i) { }
499 result = i;
500 }
501 return result;
502 }
503
504 /**
505 * Retrieve the preferred precision for the currently displayed result.
506 * May be called from non-UI thread.
507 * @param cache Current approximation as string.
508 * @param msd Position of most significant digit in result. Index in cache.
509 * Can be INVALID_MSD if we haven't found it yet.
510 * @param lastDigit Position of least significant digit (1 = tenths digit)
511 * or Integer.MAX_VALUE.
512 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700513 int getPreferredPrec(String cache, int msd, int lastDigit) {
Hans Boehm84614952014-11-25 18:46:17 -0800514 int lineLength = mResult.getMaxChars();
515 int wholeSize = cache.indexOf('.');
Hans Boehma0e45f32015-05-30 13:20:35 -0700516 int negative = cache.charAt(0) == '-' ? 1 : 0;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700517 // Don't display decimal point if result is an integer.
518 if (lastDigit == 0) lastDigit = -1;
Hans Boehma0e45f32015-05-30 13:20:35 -0700519 if (lastDigit != Integer.MAX_VALUE) {
520 if (wholeSize <= lineLength && lastDigit <= 0) {
521 // Exact integer. Prefer to display as integer, without decimal point.
522 return -1;
523 }
524 if (lastDigit >= 0 && wholeSize + lastDigit + 1 /* dec.pt. */ <= lineLength) {
525 // Display full exact number wo scientific notation.
526 return lastDigit;
527 }
Hans Boehm84614952014-11-25 18:46:17 -0800528 }
Hans Boehm50ed3202015-06-09 14:35:49 -0700529 if (msd > wholeSize && msd <= wholeSize + EXP_COST + 1) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700530 // Display number without scientific notation. Treat leading zero as msd.
Hans Boehm84614952014-11-25 18:46:17 -0800531 msd = wholeSize - 1;
532 }
Hans Boehm760a9dc2015-04-20 10:27:12 -0700533 if (msd > wholeSize + MAX_MSD_PREC) {
Hans Boehma0e45f32015-05-30 13:20:35 -0700534 // Display a probable but uncertain 0 as "0.000000000",
Hans Boehm760a9dc2015-04-20 10:27:12 -0700535 // without exponent. That's a judgment call, but less likely
536 // to confuse naive users. A more informative and confusing
537 // option would be to use a large negative exponent.
538 return lineLength - 2;
539 }
Hans Boehma0e45f32015-05-30 13:20:35 -0700540 // Return position corresponding to having msd at left, effectively
541 // presuming scientific notation that preserves the left part of the
542 // result.
543 return msd - wholeSize + lineLength - negative - 1;
Hans Boehm84614952014-11-25 18:46:17 -0800544 }
545
Hans Boehm50ed3202015-06-09 14:35:49 -0700546 private static final int SHORT_TARGET_LENGTH = 8;
547 private static final String SHORT_UNCERTAIN_ZERO = "0.00000" + KeyMaps.ELLIPSIS;
Hans Boehm013969e2015-04-13 20:29:47 -0700548
Hans Boehm50ed3202015-06-09 14:35:49 -0700549 /**
550 * Get a short representation of the value represented by the string cache.
551 * We try to match the CalculatorResult code when the result is finite
552 * and small enough to suit our needs.
553 * The result is not internationalized.
554 * @param cache String approximation of value. Assumed to be long enough
555 * that if it doesn't contain enough significant digits, we can
556 * reasonably abbreviate as SHORT_UNCERTAIN_ZERO.
557 * @param msdIndex Index of most significant digit in cache, or INVALID_MSD.
558 * @param lsd Position of least significant digit in finite representation,
559 * relative to decimal point, or MAX_VALUE.
560 */
561 private String getShortString(String cache, int msdIndex, int lsd) {
562 // This somewhat mirrors the display formatting code, but
563 // - The constants are different, since we don't want to use the whole display.
564 // - This is an easier problem, since we don't support scrolling and the length
565 // is a bit flexible.
566 // TODO: Think about refactoring this to remove partial redundancy with CalculatorResult.
567 final int dotIndex = cache.indexOf('.');
568 final int negative = cache.charAt(0) == '-' ? 1 : 0;
569 final String negativeSign = negative == 1 ? "-" : "";
570
571 // Ensure we don't have to worry about running off the end of cache.
572 if (msdIndex >= cache.length() - SHORT_TARGET_LENGTH) {
573 msdIndex = INVALID_MSD;
574 }
575 if (msdIndex == INVALID_MSD) {
576 if (lsd < INIT_PREC) {
577 return "0";
578 } else {
579 return SHORT_UNCERTAIN_ZERO;
Hans Boehm84614952014-11-25 18:46:17 -0800580 }
Hans Boehm84614952014-11-25 18:46:17 -0800581 }
Hans Boehm50ed3202015-06-09 14:35:49 -0700582 // Avoid scientific notation for small numbers of zeros.
583 // Instead stretch significant digits to include decimal point.
584 if (lsd < -1 && dotIndex - msdIndex + negative <= SHORT_TARGET_LENGTH
585 && lsd >= -CalculatorResult.MAX_TRAILING_ZEROES - 1) {
586 // Whole number that fits in allotted space.
587 // CalculatorResult would not use scientific notation either.
588 lsd = -1;
Hans Boehm013969e2015-04-13 20:29:47 -0700589 }
Hans Boehm50ed3202015-06-09 14:35:49 -0700590 if (msdIndex > dotIndex) {
591 if (msdIndex <= dotIndex + EXP_COST + 1) {
592 // Preferred display format inthis cases is with leading zeroes, even if
593 // it doesn't fit entirely. Replicate that here.
594 msdIndex = dotIndex - 1;
595 } else if (lsd <= SHORT_TARGET_LENGTH - negative - 2
596 && lsd <= CalculatorResult.MAX_LEADING_ZEROES + 1) {
597 // Fraction that fits entirely in allotted space.
598 // CalculatorResult would not use scientific notation either.
599 msdIndex = dotIndex -1;
600 }
601 }
602 int exponent = dotIndex - msdIndex;
603 if (exponent > 0) {
604 // Adjust for the fact that the decimal point itself takes space.
605 exponent--;
606 }
607 if (lsd != Integer.MAX_VALUE) {
608 int lsdIndex = dotIndex + lsd;
609 int totalDigits = lsdIndex - msdIndex + negative + 1;
610 if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsd >= -1) {
611 // Fits, no exponent needed.
612 return negativeSign + cache.substring(msdIndex, lsdIndex + 1);
613 }
614 if (totalDigits <= SHORT_TARGET_LENGTH - 3) {
615 return negativeSign + cache.charAt(msdIndex) + "."
616 + cache.substring(msdIndex + 1, lsdIndex + 1) + "e" + exponent;
617 }
618 }
619 // We need to abbreviate.
620 if (dotIndex > msdIndex && dotIndex < msdIndex + SHORT_TARGET_LENGTH - negative - 1) {
621 return negativeSign + cache.substring(msdIndex,
622 msdIndex + SHORT_TARGET_LENGTH - negative - 1) + KeyMaps.ELLIPSIS;
623 }
624 // Need abbreviation + exponent
625 return negativeSign + cache.charAt(msdIndex) + "."
626 + cache.substring(msdIndex + 1, msdIndex + SHORT_TARGET_LENGTH - negative - 4)
627 + KeyMaps.ELLIPSIS + "e" + exponent;
Hans Boehm84614952014-11-25 18:46:17 -0800628 }
629
630 // Return the most significant digit position in the given string
631 // or INVALID_MSD.
Hans Boehm08e8f322015-04-21 13:18:38 -0700632 public static int getMsdPos(String s) {
Hans Boehm84614952014-11-25 18:46:17 -0800633 int len = s.length();
634 int nonzeroPos = -1;
635 for (int i = 0; i < len; ++i) {
636 char c = s.charAt(i);
637 if (c != '-' && c != '.' && c != '0') {
638 nonzeroPos = i;
639 break;
640 }
641 }
642 if (nonzeroPos >= 0 &&
643 (nonzeroPos < len - 1 || s.charAt(nonzeroPos) != '1')) {
644 return nonzeroPos;
645 } else {
646 // Unknown, or could change on reevaluation
647 return INVALID_MSD;
648 }
Hans Boehm84614952014-11-25 18:46:17 -0800649 }
650
651 // Return most significant digit position in the cache, if determined,
652 // INVALID_MSD ow.
653 // If unknown, and we've computed less than DESIRED_PREC,
654 // schedule reevaluation and redisplay, with higher precision.
655 int getMsd() {
656 if (mMsd != INVALID_MSD) return mMsd;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700657 if (mRatVal != null && mRatVal.signum() == 0) {
Hans Boehm84614952014-11-25 18:46:17 -0800658 return INVALID_MSD; // None exists
659 }
660 int res = INVALID_MSD;
661 if (mCache != null) {
662 res = getMsdPos(mCache);
663 }
664 if (res == INVALID_MSD && mEvaluator == null
665 && mCurrentReevaluator == null && mCacheDigs < MAX_MSD_PREC) {
666 // We assert that mCache is not null, since there is no
667 // evaluator running.
668 ensureCachePrec(MAX_MSD_PREC);
669 // Could reevaluate more incrementally, but we suspect that if
670 // we have to reevaluate at all, the result is probably zero.
671 }
672 return res;
673 }
674
675 // Return a string with n placeholder characters.
676 private String getPadding(int n) {
677 StringBuilder padding = new StringBuilder();
Hans Boehm84614952014-11-25 18:46:17 -0800678 for (int i = 0; i < n; ++i) {
Hans Boehmffda5282015-05-18 15:00:12 -0700679 padding.append(' '); // To be replaced during final translation.
Hans Boehm84614952014-11-25 18:46:17 -0800680 }
681 return padding.toString();
682 }
683
684 // Return the number of zero characters at the beginning of s
685 private int leadingZeroes(String s) {
686 int res = 0;
687 int len = s.length();
688 for (res = 0; res < len && s.charAt(res) == '0'; ++res) {}
689 return res;
690 }
691
Hans Boehm08e8f322015-04-21 13:18:38 -0700692 private static final int MIN_DIGS = 5;
693 // Leave at least this many digits from the whole number
694 // part on the screen, to avoid silly displays like 1E1.
695 // Return result to exactly prec[0] digits to the right of the
696 // decimal point.
Hans Boehm84614952014-11-25 18:46:17 -0800697 // The result should be no longer than maxDigs.
Hans Boehm08e8f322015-04-21 13:18:38 -0700698 // No exponent or other indication of precision is added.
Hans Boehm84614952014-11-25 18:46:17 -0800699 // The result is returned immediately, based on the
700 // current cache contents, but it may contain question
701 // marks for unknown digits. It may also use uncertain
702 // digits within EXTRA_DIGITS. If either of those occurred,
703 // schedule a reevaluation and redisplay operation.
Hans Boehm08e8f322015-04-21 13:18:38 -0700704 // Uncertain digits never appear to the left of the decimal point.
Hans Boehm84614952014-11-25 18:46:17 -0800705 // digs may be negative to only retrieve digits to the left
Hans Boehm08e8f322015-04-21 13:18:38 -0700706 // of the decimal point. (prec[0] = 0 means we include
707 // the decimal point, but nothing to the right. prec[0] = -1
Hans Boehm84614952014-11-25 18:46:17 -0800708 // means we drop the decimal point and start at the ones
709 // position. Should not be invoked if mVal is null.
Hans Boehm08e8f322015-04-21 13:18:38 -0700710 // This essentially just returns a substring of the full result;
711 // a leading minus sign or leading digits can be dropped.
712 // Result uses US conventions; is NOT internationalized.
713 // We set negative[0] if the number as a whole is negative,
714 // since we may drop the minus sign.
715 // We set truncated[0] if leading nonzero digits were dropped.
716 // getRational() can be used to determine whether the result
717 // is exact, or whether we dropped trailing digits.
718 // If the requested prec[0] value is out of range, we update
Hans Boehma0e45f32015-05-30 13:20:35 -0700719 // it in place and use the updated value. But we do not make it
720 // greater than maxPrec.
721 public String getString(int[] prec, int maxPrec, int maxDigs,
Hans Boehm08e8f322015-04-21 13:18:38 -0700722 boolean[] truncated, boolean[] negative) {
723 int digs = prec[0];
Hans Boehm84614952014-11-25 18:46:17 -0800724 mLastDigs = digs;
725 // Make sure we eventually get a complete answer
Hans Boehm84614952014-11-25 18:46:17 -0800726 if (mCache == null) {
Hans Boehme4579122015-05-26 17:52:32 -0700727 ensureCachePrec(digs + EXTRA_DIGITS);
728 // Nothing else to do now; seems to happen on rare occasion
Hans Boehm08e8f322015-04-21 13:18:38 -0700729 // with weird user input timing;
730 // Will repair itself in a jiffy.
Hans Boehm84614952014-11-25 18:46:17 -0800731 return getPadding(1);
Hans Boehme4579122015-05-26 17:52:32 -0700732 } else {
733 ensureCachePrec(digs + EXTRA_DIGITS
734 + mCache.length() / EXTRA_DIVISOR);
Hans Boehm84614952014-11-25 18:46:17 -0800735 }
736 // Compute an appropriate substring of mCache.
737 // We avoid returning a huge string to minimize string
738 // allocation during scrolling.
739 // Pad as needed.
Hans Boehm08e8f322015-04-21 13:18:38 -0700740 final int len = mCache.length();
741 final boolean myNegative = mCache.charAt(0) == '-';
742 negative[0] = myNegative;
743 // Don't scroll left past leftmost digits in mCache
744 // unless that still leaves an integer.
Hans Boehm84614952014-11-25 18:46:17 -0800745 int integralDigits = len - mCacheDigs;
746 // includes 1 for dec. pt
Hans Boehm08e8f322015-04-21 13:18:38 -0700747 if (myNegative) --integralDigits;
748 int minDigs = Math.min(-integralDigits + MIN_DIGS, -1);
Hans Boehma0e45f32015-05-30 13:20:35 -0700749 digs = Math.min(Math.max(digs, minDigs), maxPrec);
Hans Boehm08e8f322015-04-21 13:18:38 -0700750 prec[0] = digs;
Hans Boehm84614952014-11-25 18:46:17 -0800751 int offset = mCacheDigs - digs; // trailing digits to drop
752 int deficit = 0; // The number of digits we're short
753 if (offset < 0) {
754 offset = 0;
755 deficit = Math.min(digs - mCacheDigs, maxDigs);
756 }
757 int endIndx = len - offset;
758 if (endIndx < 1) return " ";
759 int startIndx = (endIndx + deficit <= maxDigs) ?
760 0
761 : endIndx + deficit - maxDigs;
Hans Boehm08e8f322015-04-21 13:18:38 -0700762 truncated[0] = (startIndx > getMsd());
763 String res = mCache.substring(startIndx, endIndx);
Hans Boehm84614952014-11-25 18:46:17 -0800764 if (deficit > 0) {
765 res = res + getPadding(deficit);
766 // Since we always compute past the decimal point,
767 // this never fills in the spot where the decimal point
768 // should go, and the rest of this can treat the
769 // made-up symbols as though they were digits.
770 }
Hans Boehm84614952014-11-25 18:46:17 -0800771 return res;
772 }
773
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700774 // Return rational representation of current result, if any.
775 public BoundedRational getRational() {
776 return mRatVal;
777 }
778
Hans Boehm84614952014-11-25 18:46:17 -0800779 private void clearCache() {
780 mCache = null;
781 mCacheDigs = mCacheDigsReq = 0;
782 mMsd = INVALID_MSD;
783 }
784
785 void clear() {
786 mExpr.clear();
787 clearCache();
788 }
789
Hans Boehmc023b732015-04-29 11:30:47 -0700790 // Begin evaluation of result and display when ready.
791 // We assume this is called after each insertion and deletion.
792 // Thus if we are called twice with the same effective end of
793 // the formula, the evaluation is redundant.
Hans Boehm84614952014-11-25 18:46:17 -0800794 void evaluateAndShowResult() {
Hans Boehmc023b732015-04-29 11:30:47 -0700795 if (!mChangedValue) {
796 // Already done or in progress.
797 return;
798 }
Hans Boehmc023b732015-04-29 11:30:47 -0700799 clearCache();
Hans Boehmc1ea0912015-06-19 15:05:07 -0700800 // In very odd cases, there can be significant latency to evaluate.
801 // Don't show obsolete result.
802 mResult.clear();
Hans Boehmc023b732015-04-29 11:30:47 -0700803 mEvaluator = new AsyncDisplayResult(mDegreeMode, false);
804 mEvaluator.execute();
Hans Boehm187d3e92015-06-09 18:04:26 -0700805 mChangedValue = false;
Hans Boehm84614952014-11-25 18:46:17 -0800806 }
807
808 // Ensure that we either display a result or complain.
809 // Does not invalidate a previously computed cache.
Hans Boehmc023b732015-04-29 11:30:47 -0700810 // We presume that any prior result was computed using the same
811 // expression.
Hans Boehm84614952014-11-25 18:46:17 -0800812 void requireResult() {
Hans Boehmc023b732015-04-29 11:30:47 -0700813 if (mCache == null || mExpr.hasTrailingOperators()) {
Hans Boehm84614952014-11-25 18:46:17 -0800814 // Restart evaluator in requested mode, i.e. with
Hans Boehmc023b732015-04-29 11:30:47 -0700815 // longer timeout, not ignoring trailing operators.
Hans Boehmc1ea0912015-06-19 15:05:07 -0700816 cancelAll(true);
Hans Boehmc023b732015-04-29 11:30:47 -0700817 clearCache();
Hans Boehm84614952014-11-25 18:46:17 -0800818 mEvaluator = new AsyncDisplayResult(mDegreeMode, true);
819 mEvaluator.execute();
820 } else {
Hans Boehmc023b732015-04-29 11:30:47 -0700821 // Notify immediately, reusing existing result.
Hans Boehm84614952014-11-25 18:46:17 -0800822 int dotPos = mCache.indexOf('.');
823 String truncatedWholePart = mCache.substring(0, dotPos);
Hans Boehm15a853d2015-06-23 18:32:02 -0700824 int leastDigOffset = getLsd(mRatVal, mCache, dotPos);
825 int msdIndex = getMsd();
826 int preferredPrecOffset = getPreferredPrec(mCache, msdIndex, leastDigOffset);
827 mCalculator.onEvaluate(preferredPrecOffset, msdIndex, leastDigOffset,
828 truncatedWholePart);
Hans Boehm84614952014-11-25 18:46:17 -0800829 }
830 }
831
Hans Boehmc1ea0912015-06-19 15:05:07 -0700832 /**
833 * Cancel all current background tasks.
834 * @param quiet suppress cancellation message
835 * @return true if we cancelled an initial evaluation
836 */
837 boolean cancelAll(boolean quiet) {
Hans Boehm84614952014-11-25 18:46:17 -0800838 if (mCurrentReevaluator != null) {
Hans Boehm84614952014-11-25 18:46:17 -0800839 mCurrentReevaluator.cancel(true);
840 mCacheDigsReq = mCacheDigs;
841 // Backgound computation touches only constructive reals.
842 // OK not to wait.
843 mCurrentReevaluator = null;
844 }
845 if (mEvaluator != null) {
Hans Boehmc1ea0912015-06-19 15:05:07 -0700846 if (quiet) {
847 mEvaluator.suppressCancelMessage();
848 }
Hans Boehm84614952014-11-25 18:46:17 -0800849 mEvaluator.cancel(true);
850 // There seems to be no good way to wait for cancellation
851 // to complete, and the evaluation continues to look at
852 // mExpr, which we will again modify.
853 // Give ourselves a new copy to work on instead.
854 mExpr = (CalculatorExpr)mExpr.clone();
855 // Approximation of constructive reals should be thread-safe,
856 // so we can let that continue until it notices the cancellation.
857 mEvaluator = null;
Hans Boehm187d3e92015-06-09 18:04:26 -0700858 mChangedValue = true; // Didn't do the expected evaluation.
Hans Boehm84614952014-11-25 18:46:17 -0800859 return true;
860 }
861 return false;
862 }
863
864 void restoreInstanceState(DataInput in) {
Hans Boehm1176f232015-05-11 16:26:03 -0700865 mChangedValue = true;
Hans Boehm84614952014-11-25 18:46:17 -0800866 try {
867 CalculatorExpr.initExprInput();
868 mDegreeMode = in.readBoolean();
869 mExpr = new CalculatorExpr(in);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700870 mSavedName = in.readUTF();
871 mSaved = new CalculatorExpr(in);
Hans Boehm84614952014-11-25 18:46:17 -0800872 } catch (IOException e) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700873 Log.v("Calculator", "Exception while restoring:\n" + e);
Hans Boehm84614952014-11-25 18:46:17 -0800874 }
875 }
876
877 void saveInstanceState(DataOutput out) {
878 try {
879 CalculatorExpr.initExprOutput();
880 out.writeBoolean(mDegreeMode);
881 mExpr.write(out);
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700882 out.writeUTF(mSavedName);
883 mSaved.write(out);
Hans Boehm84614952014-11-25 18:46:17 -0800884 } catch (IOException e) {
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700885 Log.v("Calculator", "Exception while saving state:\n" + e);
Hans Boehm84614952014-11-25 18:46:17 -0800886 }
887 }
888
889 // Append a button press to the current expression.
Hans Boehmc023b732015-04-29 11:30:47 -0700890 // Return false if we rejected the insertion due to obvious
Hans Boehm84614952014-11-25 18:46:17 -0800891 // syntax issues, and the expression is unchanged.
892 // Return true otherwise.
893 boolean append(int id) {
Hans Boehm4db31b42015-05-31 12:19:05 -0700894 if (id == R.id.fun_10pow) {
895 add10pow(); // Handled as macro expansion.
896 return true;
897 } else {
Hans Boehm187d3e92015-06-09 18:04:26 -0700898 mChangedValue = mChangedValue || (KeyMaps.digVal(id) != KeyMaps.NOT_DIGIT
899 || KeyMaps.isSuffix(id) || id == R.id.const_pi || id == R.id.const_e);
Hans Boehm4db31b42015-05-31 12:19:05 -0700900 return mExpr.add(id);
901 }
Hans Boehm84614952014-11-25 18:46:17 -0800902 }
903
Hans Boehmc023b732015-04-29 11:30:47 -0700904 void delete() {
905 mChangedValue = true;
906 mExpr.delete();
907 }
908
Justin Klaassen8b1efdb2015-06-22 15:10:53 -0700909 void setDegreeMode(boolean degreeMode) {
Hans Boehmc023b732015-04-29 11:30:47 -0700910 mChangedValue = true;
Justin Klaassen8b1efdb2015-06-22 15:10:53 -0700911 mDegreeMode = degreeMode;
912
913 mSharedPrefs.edit()
914 .putBoolean(KEY_PREF_DEGREE_MODE, degreeMode)
915 .apply();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700916 }
917
Hans Boehmbfe8c222015-04-02 16:26:07 -0700918 boolean getDegreeMode() {
919 return mDegreeMode;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700920 }
921
Justin Klaassen44595162015-05-28 17:55:20 -0700922 /**
923 * @return the {@link CalculatorExpr} representation of the current result
924 */
925 CalculatorExpr getResultExpr() {
Hans Boehm50ed3202015-06-09 14:35:49 -0700926 final int dotPos = mCache.indexOf('.');
927 final int leastDigPos = getLsd(mRatVal, mCache, dotPos);
928 return mExpr.abbreviate(mVal, mRatVal, mDegreeMode,
929 getShortString(mCache, getMsdPos(mCache), leastDigPos));
Justin Klaassen44595162015-05-28 17:55:20 -0700930 }
931
Hans Boehm84614952014-11-25 18:46:17 -0800932 // Abbreviate the current expression to a pre-evaluated
933 // expression node, which will display as a short number.
934 // This should not be called unless the expression was
935 // previously evaluated and produced a non-error result.
936 // Pre-evaluated expressions can never represent an
937 // expression for which evaluation to a constructive real
938 // diverges. Subsequent re-evaluation will also not diverge,
939 // though it may generate errors of various kinds.
940 // E.g. sqrt(-10^-1000)
Justin Klaassen44595162015-05-28 17:55:20 -0700941 void collapse() {
942 final CalculatorExpr abbrvExpr = getResultExpr();
Hans Boehm84614952014-11-25 18:46:17 -0800943 clear();
944 mExpr.append(abbrvExpr);
Hans Boehm187d3e92015-06-09 18:04:26 -0700945 mChangedValue = true;
Hans Boehm84614952014-11-25 18:46:17 -0800946 }
947
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700948 // Same as above, but put result in mSaved, leaving mExpr alone.
949 // Return false if result is unavailable.
950 boolean collapseToSaved() {
Justin Klaassen44595162015-05-28 17:55:20 -0700951 if (mCache == null) {
952 return false;
953 }
954
955 final CalculatorExpr abbrvExpr = getResultExpr();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700956 mSaved.clear();
957 mSaved.append(abbrvExpr);
958 return true;
959 }
960
961 Uri uriForSaved() {
962 return new Uri.Builder().scheme("tag")
963 .encodedOpaquePart(mSavedName)
964 .build();
965 }
966
967 // Collapse the current expression to mSaved and return a URI
968 // describing this particular result, so that we can refer to it
969 // later.
970 Uri capture() {
971 if (!collapseToSaved()) return null;
972 // Generate a new (entirely private) URI for this result.
973 // Attempt to conform to RFC4151, though it's unclear it matters.
974 Date date = new Date();
975 TimeZone tz = TimeZone.getDefault();
976 DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
977 df.setTimeZone(tz);
978 String isoDate = df.format(new Date());
979 mSavedName = "calculator2.android.com," + isoDate + ":"
980 + (new Random().nextInt() & 0x3fffffff);
981 Uri tag = uriForSaved();
982 return tag;
983 }
984
985 boolean isLastSaved(Uri uri) {
986 return uri.equals(uriForSaved());
987 }
988
989 void addSaved() {
Hans Boehmed9e6782015-05-01 17:21:13 -0700990 mChangedValue = true;
Hans Boehm4a6b7cb2015-04-03 18:41:52 -0700991 mExpr.append(mSaved);
992 }
993
Hans Boehm4db31b42015-05-31 12:19:05 -0700994 // Add the power of 10 operator to the expression. This is treated
995 // essentially as a macro expansion.
996 private void add10pow() {
997 CalculatorExpr ten = new CalculatorExpr();
998 ten.add(R.id.digit_1);
999 ten.add(R.id.digit_0);
1000 mChangedValue = true; // For consistency. Reevaluation is probably not useful.
1001 mExpr.append(ten);
1002 mExpr.add(R.id.op_pow);
1003 }
1004
Hans Boehm84614952014-11-25 18:46:17 -08001005 // Retrieve the main expression being edited.
1006 // It is the callee's reponsibility to call cancelAll to cancel
1007 // ongoing concurrent computations before modifying the result.
1008 // TODO: Perhaps add functionality so we can keep this private?
1009 CalculatorExpr getExpr() {
1010 return mExpr;
1011 }
1012
1013}