summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Kondik <steve@cyngn.com>2015-12-07 16:58:16 -0800
committerSteve Kondik <steve@cyngn.com>2015-12-07 16:58:16 -0800
commit9000d115a8c23d81711cf4b22e5d6373c32edfad (patch)
tree648b11b4b417f53bc51024c359e52b8f2584c583
parent43346b2ba0b6c8d1537da7c8a0e1fa6a19870c37 (diff)
parent341e4d23dbbde66f837a72f0e16128705496e9a5 (diff)
downloadandroid_packages_apps_ExactCalculator-9000d115a8c23d81711cf4b22e5d6373c32edfad.tar.gz
android_packages_apps_ExactCalculator-9000d115a8c23d81711cf4b22e5d6373c32edfad.tar.bz2
android_packages_apps_ExactCalculator-9000d115a8c23d81711cf4b22e5d6373c32edfad.zip
Merge tag 'android-6.0.1_r3' of https://android.googlesource.com/platform/packages/apps/ExactCalculator into HEAD
Android 6.0.1 release 3
-rw-r--r--docs/arithmetic-overview.html271
-rw-r--r--docs/implementation-overview.html51
-rw-r--r--res/values-af/strings.xml1
-rw-r--r--res/values-am/strings.xml1
-rw-r--r--res/values-ar/strings.xml1
-rw-r--r--res/values-az-rAZ/strings.xml1
-rw-r--r--res/values-bg/strings.xml1
-rw-r--r--res/values-bn-rBD/strings.xml1
-rw-r--r--res/values-ca/strings.xml1
-rw-r--r--res/values-cs/strings.xml1
-rw-r--r--res/values-da/strings.xml1
-rw-r--r--res/values-de/strings.xml1
-rw-r--r--res/values-el/strings.xml1
-rw-r--r--res/values-en-rAU/strings.xml1
-rw-r--r--res/values-en-rGB/strings.xml1
-rw-r--r--res/values-en-rIN/strings.xml1
-rw-r--r--res/values-es-rUS/strings.xml1
-rw-r--r--res/values-es/strings.xml1
-rw-r--r--res/values-et-rEE/strings.xml1
-rw-r--r--res/values-eu-rES/strings.xml1
-rw-r--r--res/values-fa/strings.xml1
-rw-r--r--res/values-fi/strings.xml1
-rw-r--r--res/values-fr-rCA/strings.xml1
-rw-r--r--res/values-fr/strings.xml1
-rw-r--r--res/values-gl-rES/strings.xml1
-rw-r--r--res/values-gu-rIN/strings.xml1
-rw-r--r--res/values-hi/strings.xml1
-rw-r--r--res/values-hr/strings.xml1
-rw-r--r--res/values-hu/strings.xml1
-rw-r--r--res/values-hy-rAM/strings.xml1
-rw-r--r--res/values-in/strings.xml1
-rw-r--r--res/values-is-rIS/strings.xml1
-rw-r--r--res/values-it/strings.xml1
-rw-r--r--res/values-iw/strings.xml1
-rw-r--r--res/values-ja/strings.xml1
-rw-r--r--res/values-ka-rGE/strings.xml1
-rw-r--r--res/values-kk-rKZ/strings.xml1
-rw-r--r--res/values-km-rKH/strings.xml1
-rw-r--r--res/values-kn-rIN/strings.xml1
-rw-r--r--res/values-ko/strings.xml1
-rw-r--r--res/values-ky-rKG/strings.xml1
-rw-r--r--res/values-lo-rLA/strings.xml1
-rw-r--r--res/values-lt/strings.xml1
-rw-r--r--res/values-lv/strings.xml1
-rw-r--r--res/values-mk-rMK/strings.xml1
-rw-r--r--res/values-ml-rIN/strings.xml1
-rw-r--r--res/values-mn-rMN/strings.xml1
-rw-r--r--res/values-mr-rIN/strings.xml1
-rw-r--r--res/values-ms-rMY/strings.xml1
-rw-r--r--res/values-my-rMM/strings.xml1
-rw-r--r--res/values-nb/strings.xml1
-rw-r--r--res/values-ne-rNP/strings.xml1
-rw-r--r--res/values-nl/strings.xml1
-rw-r--r--res/values-pa-rIN/strings.xml1
-rw-r--r--res/values-pl/strings.xml1
-rw-r--r--res/values-pt-rPT/strings.xml1
-rw-r--r--res/values-pt/strings.xml1
-rw-r--r--res/values-ro/strings.xml1
-rw-r--r--res/values-ru/strings.xml1
-rw-r--r--res/values-si-rLK/strings.xml1
-rw-r--r--res/values-sk/strings.xml1
-rw-r--r--res/values-sl/strings.xml1
-rw-r--r--res/values-sq-rAL/strings.xml1
-rw-r--r--res/values-sr/strings.xml1
-rw-r--r--res/values-sv/strings.xml5
-rw-r--r--res/values-sw/strings.xml1
-rw-r--r--res/values-ta-rIN/strings.xml1
-rw-r--r--res/values-te-rIN/strings.xml1
-rw-r--r--res/values-th/strings.xml1
-rw-r--r--res/values-tl/strings.xml1
-rw-r--r--res/values-tr/strings.xml1
-rwxr-xr-xres/values-uk/strings.xml1
-rw-r--r--res/values-ur-rPK/strings.xml1
-rw-r--r--res/values-uz-rUZ/strings.xml1
-rw-r--r--res/values-vi/strings.xml1
-rw-r--r--res/values-zh-rCN/strings.xml1
-rw-r--r--res/values-zh-rHK/strings.xml1
-rw-r--r--res/values-zh-rTW/strings.xml1
-rw-r--r--res/values-zu/strings.xml1
-rw-r--r--res/values/strings.xml15
-rw-r--r--src/com/android/calculator2/AlertDialogFragment.java54
-rw-r--r--src/com/android/calculator2/BoundedRational.java355
-rw-r--r--src/com/android/calculator2/Calculator.java56
-rw-r--r--src/com/android/calculator2/CalculatorExpr.java783
-rw-r--r--src/com/android/calculator2/CalculatorResult.java82
-rw-r--r--src/com/android/calculator2/CalculatorText.java29
-rw-r--r--src/com/android/calculator2/Evaluator.java1161
-rw-r--r--tests/AndroidManifest.xml2
-rw-r--r--tests/README.txt11
-rw-r--r--tests/src/com/android/calculator2/BRTest.java10
-rw-r--r--tests/src/com/android/calculator2/EvaluatorTest.java59
91 files changed, 1953 insertions, 1067 deletions
diff --git a/docs/arithmetic-overview.html b/docs/arithmetic-overview.html
new file mode 100644
index 0000000..68dd24e
--- /dev/null
+++ b/docs/arithmetic-overview.html
@@ -0,0 +1,271 @@
+<!doctype html>
+<html>
+<head>
+<title>Calculator Arithmetic Overview</title>
+<meta charset="UTF-8">
+<style>
+#toc {
+ width:300px;
+ border:1px solid #ccc;
+ background-color:#efefef;
+ float:right;
+}
+.display {
+ color:#666666;
+ background-color:#f3f3f3;
+}
+</style>
+</head>
+<body onload="init();">
+<div id="toc"></div>
+<h1>Arithmetic in the Android M Calculator</h1>
+<p>Most conventional calculators, both the specialized hardware and software varieties, represent
+numbers using fairly conventional machine floating point arithmetic. Each number is stored as an
+exponent, identifying the position of the decimal point, together with the first 10 to 20
+significant digits of the number. For example, 1/300 might be stored as
+0.333333333333x10<sup>-2</sup>, i.e. as an exponent of -2, together with the 12 most significant
+digits. This is similar, and sometimes identical to, computer arithmetic used to solve large
+scale scientific problems.</p> <p>This kind of arithmetic works well most of the time, but can
+sometimes produce completely incorrect results. For example, the trigonometric tangent (tan) and
+arctangent (tan<sup>-1</sup>) functions are defined so that tan(tan<sup>-1</sup>(<i>x</i>)) should
+always be <i>x</i>. But on most calculators we have tried, tan(tan<sup>-1</sup>(10<sup>20</sup>))
+is off by at least a factor of 1000. A value around 10<sup>16</sup> or 10<sup>17</sup> is quite
+popular, which unfortunately doesn't make it correct. The underlying problem is that
+tan<sup>-1</sup>(10<sup>17</sup>) and tan<sup>-1</sup>(10<sup>20</sup>) are so close that
+conventional representations don't distinguish them. (They're both 89.9999… degrees with at least
+fifteen 9s beyond the decimal point.) But the tiny difference between them results in a huge
+difference when the tangent function is applied to the result.</p>
+
+<p>Similarly, it may be puzzling to a high school student that while the textbook claims that for
+any <i>x</i>, sin(<i>x</i>) + sin(<i>x</i>+π) = 0, their calculator says that sin(10<sup>15</sup>)
++ sin(10<sup>15</sup>+π) = <span class="display">-0.00839670971</span>. (Thanks to floating point
+standardization, multiple on-line calculators agree on that entirely bogus value!)</p>
+
+<p>We know that the instantaneous rate of change of a function f, its derivative, can be
+approximated at a point <i>x</i> by computing (<i>f</i>(<i>x</i> + <i>h</i>) - <i>f</i>(<i>x</i>))
+/ <i>h</i>, for very small <i>h</i>. Yet, if you try this in a conventional calculator with
+<i>h</i> = 10<sup>-20</sup> or smaller, you are unlikely to get a useful answer.</p>
+
+<p>In general these problems occur when computations amplify tiny errors, a problem referred to as
+numerical instability. This doesn't happen very often, but as in the above examples, it may
+require some insight to understand when it can and can't happen.</p>
+
+<p>In large scale scientific computations, hardware floating point computations are essential
+since they are the only reasonable way modern computer hardware can produce answers with
+sufficient speed. Experts must be careful to structure computations to avoid such problems. But
+for "computing in the small" problems, like those solved on desk calculators, we can do much
+better!</p>
+
+<h2>Producing accurate answers</h2>
+<p>The Android M Calculator uses a different kind of computer arithmetic. Rather than computing a
+fixed number of digits for each intermediate result, the computation is much more goal directed.
+The user would like to see only correct digits on the display, which we take to mean that the
+displayed answer should always be off by less than one in the last displayed digit. The
+computation is thus performed to whatever precision is required to achieve that.</p>
+
+<p>Let's say we want to compute π+⅓, and the calculator display has 10 digits. We'd compute both π
+and ⅓ to 11 digits each, add them, and round the result to 10 digits. Since π and ⅓ were accurate
+to within 1 in the 11<sup>th</sup> digit, and rounding adds an error of at most 5 in the
+11<sup>th</sup> digit, the result is guaranteed accurate to less than 1 in the 10<sup>th</sup>
+digit, which was our goal.</p>
+
+<p>This is of course an oversimplification of the real implementation. Operations other than
+addition do get appreciably more complicated. Multiplication, for example, requires that we
+approximate one argument in order to determine how much precision we need for the other argument.
+The tangent function requires very high precision for arguments near 90 degrees to produce
+meaningful answers. And so on. And we really use binary rather than decimal arithmetic.
+Nonetheless the above addition method is a good illustration of the approach.</p>
+
+<p>Since we have to be able to produce answers to arbitrary precision, we can also let the user
+specify how much precision she wants, and use that as our goal. In the Android M Calculator, the
+user specifies the requested precision by scrolling the result. As the result is being scrolled,
+the calculator reevaluates it to the newly requested precision. In some cases, the algorithm for
+computing the new higher precision result takes advantage of the old, less accurate result. In
+other cases, it basically starts from scratch. Fortunately modern devices and the Android runtime
+are fast enough that the recomputation delay rarely becomes visible.</p>
+
+<h2>Design Decisions and challenges</h2>
+<p>This form of evaluate-on-demand arithmetic has occasionally been used before, and we use a
+refinement of a previously developed open source package in our implementation. However, the
+scrolling interface, together with the practicailities of a usable general purpose calculator,
+presented some new challenges. These drove a number of not-always-obvious design decisions which
+briefly describe here.</p>
+
+<h3>Indicating position</h3>
+<p>We would like the user to be able to see at a glance which part of the result is currently
+being displayed.</p>
+
+<p>Conventional calculators solve the vaguely similar problem of displaying very large or very
+small numbers by using scientific notation: They display an exponent in addition to the most
+significant digits, analogously to the internal representation they use. We solve that problem in
+exactly the same way, in spite of our different internal representation. If the user enters
+"1÷3⨉10^20", computing ⅓ times 10 to the 20th power, the result may be displayed as <span
+class="display">3.3333333333E19</span>, indicating that the result is approximately 3.3333333333
+times 10<sup>19</sup>. In this version of scientific notation, the decimal point is always
+displayed immediately to the right of the most significant digit, and the exponent indicates where
+it really belongs.</p>
+
+<p>Once the decimal point is scrolled off the display, this style of scientific notation is not
+helpful; it essentially tells us where the decimal point is relative to the most significant
+digit, but the most significant digit is no longer visible. We address this by switching to a
+different variant of scientific notation, in which we interpret the displayed digits as a whole
+number, with an implied decimal point on the right. Instead of displaying <span
+class="display">3.3333333333E19</span>, we hypothetically could display <span
+class="display">33333333333E9</span> or 33333333333 times 10<sup>9</sup>. In fact, we use this
+format only when the normal scientific notation decimal point would not be visible. If we had
+scrolled the above result 2 digits to the left, we would in fact be seeing <span
+ass="display">...33333333333E7</span>. This tells us that the displayed result is very close to a
+whole number ending in 33333333333 times 10<sup>7</sup>. Effectively the <span
+class="display">E7</span> is telling us that the last displayed digit corresponds to the ten
+millions position. In this form, the exponent does tell us the current position in the result.
+The two forms are easily distinguishable by the presence or absence of a decimal point, and the
+ellipsis character at the beginning.</p>
+
+<h3>Rounding vs. scrolling</h3>
+<p>Normally we expect calculators to try to round to the nearest displayable result. If the
+actual computed result were 0.66666666666667, and we could only display 10 digits, we would expect
+a result display of, for example <span class="display">0.666666667</span>, rather than <span
+class="display">0.666666666</span>. For us, this would have the disadvantage that when we
+scrolled the result left to see more digits, the "7" on the right would change to a "6". That
+would be mildly unfortunate. It would be somewhat worse that if the actual result were exactly
+0.99999999999, and we could only display 10 characters at a time, we would see an initial display
+of <span class="display">1.00000000</span>. As we scroll to see more digits, we would
+successively see <span class="display">...000000E-6</span>, then <span
+class="display">...000000E-7</span>, and so on until we get to <span
+class="display">...00000E-10</span>, but then suddenly <span class="display">...99999E-11</span>.
+If we scroll back, the screen would again show zeroes. We decided this would be excessively
+confusing, and thus do not round.</p>
+
+<p>It is still possible for previously displayed digits to change as we're scrolling. But we
+always compute a number of digits more than we actually need, so this is exceedingly unlikely.</p>
+
+<p>Since our goal is an error of strictly less than one in the last displayed digit, we will
+never, for example, display an answer of exactly 2 as <span class="display">1.9999999999</span>.
+That would involve an error of exactly one in the last place, which is too much for us.</p> <p>It
+turns out that there is exactly one case in which the display switches between 9s and 0s: A long
+but finite sequence of 9s (more than 20) in the true result can initially be displayed as a larger
+number ending in 0s. As we scroll, the 0s turn into 9s. When we immediately scroll back, the
+number remains displayed as 9s, since the calculator caches the best known result (though not
+currently across restarts or screen rotations).</p>
+
+<p>We prevent 9s from turning into 0s during scrolling. If we generate a result ending in 9s, our
+error bound implies that the true result is strictly less (in absolute value) than the value
+(ending in 0s) we would get by incrementing the last displayed digit. Thus we can never be forced
+back to generating zeros and will always continue to generate 9s.</p>
+
+<h3>Coping with mathematical limits</h3>
+<p>Internally the calculator essentially represents a number as a program for computing however
+many digits we happen to need. This representation has many nice properties, like never resulting
+in the display of incorrect results. It has one inherent weakness: We provably cannot compute
+precisely whether two numbers are equal. We can compute more and more digits of both numbers, and
+if they ever differ by more than one in the last computed digit, we know they are <i>not</i>
+equal. But if the two numbers were in fact the same, this process will go on forever.</p>
+
+<p>This is still better than machine floating point arithmetic, though machine floating point
+better obscures the problem. With machine floating point arithmetic, two computations that should
+mathematically have given the same answer, may give us substantially different answers, and two
+computations that should have given us different answers may easily produce the same one. We
+can indeed determine whether the floating representations are equal, but this tells us little
+about equality of the true mathematical answers.</p>
+
+<p>The undecidability of equality creates some interesting issues. If we divide a number by
+<i>x</i>, the calculator will compute more and more digits of <i>x</i> until it finds some nonzero
+ones. If <i>x</i> was in fact exactly zero, this process will continue forever.</p> <p>We deal
+with this problem using two complementary techniques:</p>
+
+<ol>
+<li>We always run numeric computations in the background, where they won't interfere with user
+interactions, just in case they take a long time. If they do take a really long time, we time
+them out and inform the user that the computation has been aborted. This is unlikely to happen by
+accident, unless the user entered an ill-defined mathematical expression, like a division by
+zero.</li>
+<li>As we will see below, in many cases we use an additional number representation that does allow
+us to determine that a number is exactly zero. Although this easily handles most cases, it is not
+foolproof. If the user enters "1÷0" we immediately detect the division by zero. If the user
+enters "1÷(π−π)" we time out. (We might choose to explicitly recognize such simple cases in the
+future. But this would always remain a heuristic.)</li>
+</ol>
+
+<h3>Zeros further than the eye can see</h3>
+<p>Prototypes of the M calculator, like mathematicians, treated all real numbers as infinite
+objects, with infinitely many digits to scroll through. If the actual computation happened to be
+2−1, the result was initially displayed as <span class="display">1.00000000</span>, and the user
+could keep scrolling through as many thousands of zeroes to the right of that as he desired.
+Although mathematically sound, this proved unpopular for several good reasons, the first one
+probably more serious than the others:</p>
+
+<ol>
+<li>If we computed $1.23 + $7.89, the result would show up as <span
+class="display">9.1200000000</span> or the like, which is unexpected and harder to read quickly
+than <span class="display">9.12</span>.</li>
+<li>Many users consider the result of 2-1 to be a finite number, and find it confusing to be able
+to scroll through lots of zeros on the right.</li>
+<li>Since the calculator couldn't ever tell that a number wasn't going to be scrolled, it couldn't
+treat any result as short enough to allow the use of a larger font.</li>
+</ol>
+
+<p>As a result, the calculator now also tries to compute the result as an exact fraction whenever
+that is easily possible. It is then easy to tell from the fraction whether a number has a finite
+decimal expansion. If it does, we prevent scrolling past that point, and may use the fact that
+the result has a short representation to increase the font size. Results displayed in a larger
+font are not scrollable. We no longer display any zeros for non-zero results unless there is
+either a nonzero or a displayed decimal point to the right. The fact that a result is not
+scrollable tells the user that the result, as displayed, is exact. This is fallible in the other
+direction. For example, we do not compute a rational representation for π−π, and hence it is
+still possible to scroll through as many zeros of that result as you like.</p>
+
+<p>This underlying fractional representation of the result is also used to detect, for example,
+division by zero without a timeout.</p>
+
+<p>Since we calculate the fractional result when we can in any case, it is also now available to
+the user through the overflow menu.</p>
+
+<h2>More details</h2>
+<p>The underlying evaluate-on-demand arithmetic package is described in H. Boehm, "The
+Constructive Reals as a Java Library'', Special issue on practical development of exact real
+number computation, <i>Journal of Logic and Algebraic Programming 64</i>, 1, July 2005, pp. 3-11.
+(Also at <a href="http://www.hpl.hp.com/techreports/2004/HPL-2004-70.html">http://www.hpl.hp.com/techreports/2004/HPL-2004-70.html</a>)</p>
+
+<p>Our version has been slightly refined. Notably it calculates inverse trigonometric functions
+directly instead of using a generic "inverse" function. This is less elegant, but significantly
+improves performance.</p>
+
+</body>
+</html>
+<script type="text/javascript">
+function generateTOC (rootNode, startLevel) {
+ var lastLevel = 0;
+ startLevel = startLevel || 2;
+ var html = "<ul>";
+
+ for (var i = 0; i < rootNode.childNodes.length; ++i) {
+ var node = rootNode.childNodes[i];
+ if (!node.tagName || !/H[1-6]/.test(node.tagName)) {
+ continue;
+ }
+ var level = +node.tagName.substr(1);
+ if (level < startLevel) { continue; }
+ var name = node.innerText;
+ if (node.children.length) { name = node.childNodes[0].innerText; }
+ if (!name) { continue; }
+ var hashable = name.replace(/[.\s\']/g, "-");
+ node.id = hashable;
+ if (level > lastLevel) {
+ html += "";
+ } else if (level < lastLevel) {
+ html += (new Array(lastLevel - level + 2)).join("</ul></li>");
+ } else {
+ html += "</ul></li>";
+ }
+ html += "<li><a class='lvl"+level+"' href='#" + hashable + "'>" + name + "</a><ul>";
+ lastLevel = level;
+ }
+
+ html += "</ul>";
+ return html;
+}
+
+function init() {
+ document.getElementById("toc").innerHTML = generateTOC(document.body);
+}
+</script>
diff --git a/docs/implementation-overview.html b/docs/implementation-overview.html
new file mode 100644
index 0000000..a06e73b
--- /dev/null
+++ b/docs/implementation-overview.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<html>
+<head>
+<title>Calculator Implementation Overview</title>
+<meta charset="UTF-8">
+</head>
+<h1>M Calculator Implementation Overview</h1>
+<p>Although the appearance of the calculator has changed little from Lollipop, and some of the UI
+code is indeed the same, the rest of the code has changed substantially. Unsurprisingly,
+<b>Calculator.java</b> implements the main UI. The other major parts of the implementation
+are:</p>
+
+<p><b>CR.java</b> in <b>external/crcalc</b> provides the underlying demand-driven ("constructive
+real") arithmetic implementation. Numbers are represented primarily as objects with a method that
+can compute arbitrarily precise approximations. The actual arithmetic performed by these methods
+is based on Java's <tt>java.util.BigInteger</tt> arithmetic, with appropriate implicit
+scaling.</p>
+
+<p><b>BoundedRational.java</b> is a rational arithmetic package that is used to provide finite
+exact answers in "easy" cases. It is used primarily to determine when an approximation provided
+by CR.java is actually exact. This is used in turn both to limit the length of displayed results
+and scrolling, as well as to identify errors such as division by zero, that would otherwise result
+in timeouts during computations. It is in some sense not needed to produce correct results, but
+it significantly improves the usability of the calculator. It is also used for the "display as
+fraction" option in the overflow menu.</p>
+
+<p><b>CalculatorExpr.java</b> implements calculator arithmetic expressions. It supports editing,
+saving, restoring, and evaluation of expressions. Evaluation produces a constructive real (CR)
+and possibly a BoundedRational result. Unlike the "arity" library used in earlier versions, the
+underlying expression is represented as a sequence of "tokens", many of which are represented by
+Button ids, not as a character string.</p>
+
+<p><b>Evaluator.java</b> implements much of the actual calculator logic, particularly background
+expression evaluation. Expression evaluation here includes both using CalculatorExpr.java to
+evaluate the expression, and then invoking the resulting CR value to actually produce finite
+approximations and convert them to decimal. Two types of expression evaluation are supported:
+(1) Initial evaluation of the expression and producing an initial decimal approximation, and (2)
+reevaluation to higher precision. (1) is invoked directly from the Calculator UI, while (2) is
+invoked from the calculator display, commonly in response to scrolling. When the display requests
+a result, a "result" is immediately returned, though it may contains blank placeholders. The
+display is then notified when the real result becomes available.</p>
+
+<p><b>CalculatorText.java</b> is the TextView subclass used to display the formula.</p>
+
+<p><b>CalculatorResult.java</b> is the TextView subclass used to display the result. It handles
+result formatting, scrolling, etc. After the user hits "=", the CalculatorResult widget moves
+into the top position, replacing the formula display. Currently it remains in that position until
+the formula is again modified.</p>
+</body>
+</html>
+
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 1b14ac4..3c30ab1 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"uitgevee"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler-nommer"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b18eb0a..0d84edf 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"ዲግ"</string>
<string name="mode_rad" msgid="1434228830085760996">"ራዲ"</string>
<string name="clr" msgid="6730945431543327337">"አጽዳ"</string>
+ <string name="cleared" msgid="3952521190281880880">"ጸድቷል"</string>
<string name="del" msgid="5878069000864178910">"ሰርዝ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"የዩለር ቁጥር"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"ፓይ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 9ee8322..1a8f3d7 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"محو"</string>
+ <string name="cleared" msgid="3952521190281880880">"تم المحو"</string>
<string name="del" msgid="5878069000864178910">"حذف"</string>
<string name="desc_const_e" msgid="1889187337970539507">"رقم أويلر"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"باي"</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 7fb2328..09f7cb1 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"dər"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"təmizlənib"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler nömrəsi"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index d9c47e7..f442b04 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"изчистено"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Неперово число"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"пи"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index b12148f..68f2f23 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"সাফ করা হয়েছে"</string>
<string name="del" msgid="5878069000864178910">"সাফ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ইউলার সংখ্যা"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"পাই"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index ce01392..356ce3d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"graus"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"esb"</string>
+ <string name="cleared" msgid="3952521190281880880">"esborrat"</string>
<string name="del" msgid="5878069000864178910">"sup"</string>
<string name="desc_const_e" msgid="1889187337970539507">"nombre d\'Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index cd77b15..7c49941 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"CE"</string>
+ <string name="cleared" msgid="3952521190281880880">"vymazáno"</string>
<string name="del" msgid="5878069000864178910">"←"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerovo číslo"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pí"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 101d166..05030b7 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grad"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"ryd"</string>
+ <string name="cleared" msgid="3952521190281880880">"ryddet"</string>
<string name="del" msgid="5878069000864178910">"slet"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulers tal"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 347e18b..7441e17 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"Grad"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"Gelöscht"</string>
<string name="del" msgid="5878069000864178910">"CE"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulersche Zahl"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"Pi"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index d64d34e..ea025ab 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"°"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"διαγράφηκε"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Αριθμός Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"πι"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 8975053..15fd704 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"cleared"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler\'s number"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 8975053..15fd704 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"cleared"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler\'s number"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 8975053..15fd704 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"cleared"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler\'s number"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 7a0e41b..d1ae903 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"º"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"be"</string>
+ <string name="cleared" msgid="3952521190281880880">"borrados"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"número de Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 57bff1f..fea364c 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grad"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"bor"</string>
+ <string name="cleared" msgid="3952521190281880880">"borrados"</string>
<string name="del" msgid="5878069000864178910">"ce"</string>
<string name="desc_const_e" msgid="1889187337970539507">"número de Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 5f31f8e..1ac1fb0 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"tüh"</string>
+ <string name="cleared" msgid="3952521190281880880">"kustutatud"</string>
<string name="del" msgid="5878069000864178910">"kus"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euleri arv"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pii"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index a87122f..0f9000e 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"°"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"CE"</string>
+ <string name="cleared" msgid="3952521190281880880">"garbituta"</string>
<string name="del" msgid="5878069000864178910">"C"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerren zenbakia"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi zenbakia"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index ce2ff26..fa89167 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"درجه"</string>
<string name="mode_rad" msgid="1434228830085760996">"راد"</string>
<string name="clr" msgid="6730945431543327337">"پاک"</string>
+ <string name="cleared" msgid="3952521190281880880">"پاک شد"</string>
<string name="del" msgid="5878069000864178910">"حذف"</string>
<string name="desc_const_e" msgid="1889187337970539507">"عدد اولر"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"پی"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index ddeebb1..78daa5a 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"thj"</string>
+ <string name="cleared" msgid="3952521190281880880">"tyhjennetty"</string>
<string name="del" msgid="5878069000864178910">"pst"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerin luku"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pii"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 7ef46c3..8f1c1ef 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"eff"</string>
+ <string name="cleared" msgid="3952521190281880880">"effacés"</string>
<string name="del" msgid="5878069000864178910">"sup"</string>
<string name="desc_const_e" msgid="1889187337970539507">"nombre d\'Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index e7740b9..a7f558e 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"eff"</string>
+ <string name="cleared" msgid="3952521190281880880">"effacés"</string>
<string name="del" msgid="5878069000864178910">"sup"</string>
<string name="desc_const_e" msgid="1889187337970539507">"nombre d\'Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 56f91c0..cd24b9d 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grao"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"bor"</string>
+ <string name="cleared" msgid="3952521190281880880">"borrados"</string>
<string name="del" msgid="5878069000864178910">"eli"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Número de Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 6785c98..6e2a444 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"સાફ કર્યા"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"યુલરનો નંબર"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"પાઇ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index a757d16..6c23e92 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"साफ़ किए गए"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"यूलर की संख्या"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"पाई"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 53cbfc2..aa485a3 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"stup."</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"izbrisano"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerov broj"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index fe64283..607a7f1 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"törölve"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler-féle szám"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pí"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 4fec08e..caaa38b 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"աստ"</string>
<string name="mode_rad" msgid="1434228830085760996">"ռադ"</string>
<string name="clr" msgid="6730945431543327337">"մքր"</string>
+ <string name="cleared" msgid="3952521190281880880">"ջնջվեց"</string>
<string name="del" msgid="5878069000864178910">"ջնջ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Էյլերյան թիվ"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"պի"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index bddad90..62e0b7b 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"dihapus"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"bilangan Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index dadb618..10978b7 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grá"</string>
<string name="mode_rad" msgid="1434228830085760996">"bog"</string>
<string name="clr" msgid="6730945431543327337">"hre"</string>
+ <string name="cleared" msgid="3952521190281880880">"hreinsað"</string>
<string name="del" msgid="5878069000864178910">"eyð"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Tala Eulers"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pí"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 8e08cd4..bf4d880 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"c"</string>
+ <string name="cleared" msgid="3952521190281880880">"cancellati"</string>
<string name="del" msgid="5878069000864178910">"ce"</string>
<string name="desc_const_e" msgid="1889187337970539507">"numero di Eulero"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index caa887a..6d24ffc 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"נוקה"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"‏הקבוע e"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"פאי"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 60d0def..45a62c1 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"消去しました"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"自然対数の底"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"パイ"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index c98975b..16febeb 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"გრად."</string>
<string name="mode_rad" msgid="1434228830085760996">"რად."</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"გასუფთავდა"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ეილერის რიცხვი"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 6dd54a3..7a19b9c 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"тзл"</string>
+ <string name="cleared" msgid="3952521190281880880">"тазартылған"</string>
<string name="del" msgid="5878069000864178910">"жою"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Эйлер саны"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"пи"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 12d938e..4b6ec9e 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"បានសម្អាត"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"លេខ Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index 3a73e85..5d380f2 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"ತೆರ"</string>
+ <string name="cleared" msgid="3952521190281880880">"ತೆರವುಗೊಳಿಸಲಾಗಿದೆ"</string>
<string name="del" msgid="5878069000864178910">"ಅಳಿ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ಯೂಲರ್‌ನ ಸಂಖ್ಯೆ"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"ಪೈ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 506a23c..a594dd4 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"삭제됨"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"오일러의 수"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"원주율(파이)"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index 3ac7ea2..2509ec2 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"град"</string>
<string name="mode_rad" msgid="1434228830085760996">"радиан"</string>
<string name="clr" msgid="6730945431543327337">"жтч"</string>
+ <string name="cleared" msgid="3952521190281880880">"тазаланды"</string>
<string name="del" msgid="5878069000864178910">"өчр"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Эйлер саны"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"Пи"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index c850e83..1b14da5 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"ອົງ​ສາ"</string>
<string name="mode_rad" msgid="1434228830085760996">"ຣາ​ດຽງ"</string>
<string name="clr" msgid="6730945431543327337">"ລຶບລ້າງ"</string>
+ <string name="cleared" msgid="3952521190281880880">"ລ້າງ​ແລ້ວ"</string>
<string name="del" msgid="5878069000864178910">"ລຶບ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ຈຳ​ນວນ​ຄ່າ​ຄົງ​ທີ່"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"​ປີ່"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 216c22a..2faf0a0 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"lpsn."</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"išv."</string>
+ <string name="cleared" msgid="3952521190281880880">"išvalyta"</string>
<string name="del" msgid="5878069000864178910">"išt."</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerio skaičius"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 7642aa1..185798d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grādi"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"C"</string>
+ <string name="cleared" msgid="3952521190281880880">"notīrīts"</string>
<string name="del" msgid="5878069000864178910">"CE"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eilera skaitlis"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pī"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index e88b366..f7af89f 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"исчистено"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Ојлеров број"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"Пи"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index 11f5437..e21e42b 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"ഡിഗ്രി"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"മായ്ച്ചു"</string>
<string name="del" msgid="5878069000864178910">"മായ്ക്കൂ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"യൂളറിന്റെ നമ്പർ"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"പൈ"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 245088f..a446853 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"хэм"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"Цэвэрлэсэн"</string>
<string name="del" msgid="5878069000864178910">"устгах"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Эйлерийн тоо"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"пи тоо"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index 12b473e..e3f10b8 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"अंश"</string>
<string name="mode_rad" msgid="1434228830085760996">"रॅडियन"</string>
<string name="clr" msgid="6730945431543327337">"साफ करा"</string>
+ <string name="cleared" msgid="3952521190281880880">"साफ केले"</string>
<string name="del" msgid="5878069000864178910">"हटवा"</string>
<string name="desc_const_e" msgid="1889187337970539507">"यूलरची संख्या"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"पाय"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index 49aee13..bec9757 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"drjh"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"ksg"</string>
+ <string name="cleared" msgid="3952521190281880880">"dikosongkan"</string>
<string name="del" msgid="5878069000864178910">"pdm"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Nombor Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index dfb6bb2..61f124d 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"ရှင်းပြီးပါပြီ"</string>
<string name="del" msgid="5878069000864178910">"ဖျက်ရန်ခလုတ်"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler\'s ဂဏန်း"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"ပိုင်"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 2318200..fab30cd 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grad"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"fjernet"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulers tall"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index 79c6b25..fc96071 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"डिग्री"</string>
<string name="mode_rad" msgid="1434228830085760996">"र्याड"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"मेटाइयो"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"युलरको नम्बर"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index f9fef34..5d974bc 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"gr"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"wis"</string>
+ <string name="cleared" msgid="3952521190281880880">"gewist"</string>
<string name="del" msgid="5878069000864178910">"←"</string>
<string name="desc_const_e" msgid="1889187337970539507">"constante van Neper"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index e1a7b30..603a0ce 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"ਡਿਗਰੀ"</string>
<string name="mode_rad" msgid="1434228830085760996">"ਰੈਡ"</string>
<string name="clr" msgid="6730945431543327337">"ਸਾਫ਼"</string>
+ <string name="cleared" msgid="3952521190281880880">"ਸਾਫ਼ ਕੀਤਾ ਗਿਆ"</string>
<string name="del" msgid="5878069000864178910">"ਮਿਟਾਓ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler ਦੀ ਸੰਖਿਆ"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 0aa0bf8..2e372a4 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"wyc"</string>
+ <string name="cleared" msgid="3952521190281880880">"wyczyszczone"</string>
<string name="del" msgid="5878069000864178910">"←"</string>
<string name="desc_const_e" msgid="1889187337970539507">"liczba Eulera"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 5614a8e..85fb586 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"limpo"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Número de Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index ea819bf..4d03145 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"graus"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"limp"</string>
+ <string name="cleared" msgid="3952521190281880880">"apagados"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Número de Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 61302f9..9e3027b 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grad"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"gol"</string>
+ <string name="cleared" msgid="3952521190281880880">"șters"</string>
<string name="del" msgid="5878069000864178910">"ștr"</string>
<string name="desc_const_e" msgid="1889187337970539507">"numărul lui Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index a27c033..8116280 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"сброшено"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"число Эйлера"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"число \"пи\""</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 53fcbae..811519b 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"හිස් කරන ලදී"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ඔයිලර් අංකය"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"පයි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index bd01691..dddff1f 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"vymazané"</string>
<string name="del" msgid="5878069000864178910">"←"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerovo číslo"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pí"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 852e4cb..187dd9a 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"sto"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"poč"</string>
+ <string name="cleared" msgid="3952521190281880880">"izbrisano"</string>
<string name="del" msgid="5878069000864178910">"izb"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulerjevo število"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 9f03ed1..14eaaf7 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"grd"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"pst"</string>
+ <string name="cleared" msgid="3952521190281880880">"u pastruan"</string>
<string name="del" msgid="5878069000864178910">"fsh"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Konstante matematikore"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 7acbbc9..a039a4d 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"обрисано"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Ојлеров број"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"пи"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1dd538c..d4c35fe 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"rensad"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eulers tal"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
@@ -65,10 +66,10 @@
<string name="cancelled" msgid="1618889609742874632">"Beräkningen har avbrutits"</string>
<string name="timeout" msgid="802690170415591535">"Tidsgränsen har uppnåtts. Värdet kan vara oändligt eller odefinierat."</string>
<string name="ok_remove_timeout" msgid="8344529153919268011">"Använd längre tidsgränser"</string>
- <string name="dismiss" msgid="7872443888515066216">"Ignorera"</string>
+ <string name="dismiss" msgid="7872443888515066216">"Stäng"</string>
<string name="exact" msgid="2597237880041789948">"(exakt)"</string>
<string name="approximate" msgid="7117143366610670836">"(±1 i sista siffran)"</string>
<string name="menu_leading" msgid="2338520833272667740">"Förstasiffror"</string>
- <string name="menu_fraction" msgid="1247477377840252234">"Som fraktion"</string>
+ <string name="menu_fraction" msgid="1247477377840252234">"Svar i bråkform"</string>
<string name="menu_licenses" msgid="1890541368064108592">"Öppen källkod"</string>
</resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 81829c6..2225162 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"digrii"</string>
<string name="mode_rad" msgid="1434228830085760996">"rediani"</string>
<string name="clr" msgid="6730945431543327337">"futa"</string>
+ <string name="cleared" msgid="3952521190281880880">"zimefutwa"</string>
<string name="del" msgid="5878069000864178910">"futa"</string>
<string name="desc_const_e" msgid="1889187337970539507">"nambari ya Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index ca6163e..2e044e2 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"டிகிரி"</string>
<string name="mode_rad" msgid="1434228830085760996">"ரேடியன்"</string>
<string name="clr" msgid="6730945431543327337">"அழி"</string>
+ <string name="cleared" msgid="3952521190281880880">"அழிக்கப்பட்டது"</string>
<string name="del" msgid="5878069000864178910">"நீக்கு"</string>
<string name="desc_const_e" msgid="1889187337970539507">"ஆய்லரின் எண்"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"பை"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 0c153f3..73c5096 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"ఖాళీ చేయబడింది"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"యూలర్ సంఖ్య"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 9cec2f2..28641af 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"ล้าง"</string>
+ <string name="cleared" msgid="3952521190281880880">"ล้างข้อมูลแล้ว"</string>
<string name="del" msgid="5878069000864178910">"ลบ"</string>
<string name="desc_const_e" msgid="1889187337970539507">"จำนวนออยเลอร์"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"พาย"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 3401cfd..8cb141a 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"na-clear"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler\'s number"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 8113e11..996a2df 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"drc"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"tmz"</string>
+ <string name="cleared" msgid="3952521190281880880">"temizlendi"</string>
<string name="del" msgid="5878069000864178910">"sil"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Euler sayısı"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 8449eff..c98ede1 100755
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"очс"</string>
+ <string name="cleared" msgid="3952521190281880880">"очищено"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"число Ейлера"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"пі"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index fccec4c..5bd6f2f 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"صاف ہوگیا"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"‏Euler کا نمبر"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index a6eab7d..0f18437 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"tozalandi"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Eyler soni"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi soni"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 128736c..a12368d 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"xóa"</string>
+ <string name="cleared" msgid="3952521190281880880">"đã xóa"</string>
<string name="del" msgid="5878069000864178910">"xóa"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Số Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index bc4f337..6b1a045 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"清除"</string>
+ <string name="cleared" msgid="3952521190281880880">"已清除"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"欧拉数"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"圆周率"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 1b7cc04..cfd5565 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"CLR"</string>
+ <string name="cleared" msgid="3952521190281880880">"已清除"</string>
<string name="del" msgid="5878069000864178910">"DEL"</string>
<string name="desc_const_e" msgid="1889187337970539507">"歐拉數"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"圓周率"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index d89eeaa..c13934b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"清除完畢"</string>
<string name="del" msgid="5878069000864178910">"del"</string>
<string name="desc_const_e" msgid="1889187337970539507">"尤拉數"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"圓周率符號"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index b9372c6..65ac7a9 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -22,6 +22,7 @@
<string name="mode_deg" msgid="1146123354434332479">"deg"</string>
<string name="mode_rad" msgid="1434228830085760996">"rad"</string>
<string name="clr" msgid="6730945431543327337">"clr"</string>
+ <string name="cleared" msgid="3952521190281880880">"kusuliwe"</string>
<string name="del" msgid="5878069000864178910">"sula"</string>
<string name="desc_const_e" msgid="1889187337970539507">"Inombolo ye-Euler"</string>
<string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 959b979..50bc983 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -118,6 +118,10 @@
<string name="eq" translatable="false">=</string>
<!-- Clear button to clear the currently entered expression. [CHAR_LIMIT=4] -->
<string name="clr">clr</string>
+ <!--
+ Announced when all characters are removed from Formula, e.g. after clear. [CHAR_LIMIT=NONE]
+ -->
+ <string name="cleared">cleared</string>
<!-- Delete button to remove last entered token. [CHAR_LIMIT=4] -->
<string name="del">del</string>
<!-- Toggle button to show/hide inverse functions. [CHAR_LIMIT=4] -->
@@ -197,9 +201,12 @@
<!-- Content description for '=' button. [CHAR_LIMIT=NONE] -->
<string name="desc_eq">equals</string>
- <!-- Content description for "clr" button. [CHAR_LIMIT=NONE] -->
+ <!-- Content description for "clr" button. Deletes entire formula. [CHAR_LIMIT=NONE] -->
<string name="desc_clr">clear</string>
- <!-- Content description for "del" button. [CHAR_LIMIT=NONE] -->
+ <!--
+ Content description for "del" button. Deletes single character. Should differ
+ from desc_clr. [CHAR_LIMIT=NONE]
+ -->
<string name="desc_del">delete</string>
<!-- Content description for "inv" button to show inverse functions. [CHAR_LIMIT=NONE] -->
<string name="desc_inv_off">show inverse functions</string>
@@ -221,7 +228,7 @@
be used, e.g. "Division by 0". Exceeding the limit will result in a truncated string.
[CHAR_LIMIT=20]
- -->
+ -->
<string name="error_zero_divide">Can\'t divide by 0</string>
<!-- Toast shown when text is copied to the clipboard. [CHAR_LIMIT=40] -->
@@ -239,7 +246,7 @@
"timing out".
[CHAR_LIMIT=40]
- -->
+ -->
<string name="ok_remove_timeout">Use longer timeouts</string>
<!-- Button label to dismiss informative text message. [CHAR_LIMIT=40] -->
<string name="dismiss">Dismiss</string>
diff --git a/src/com/android/calculator2/AlertDialogFragment.java b/src/com/android/calculator2/AlertDialogFragment.java
index bb7a50b..49f9549 100644
--- a/src/com/android/calculator2/AlertDialogFragment.java
+++ b/src/com/android/calculator2/AlertDialogFragment.java
@@ -21,22 +21,50 @@ import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
+import android.content.DialogInterface;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.widget.TextView;
-public class AlertDialogFragment extends DialogFragment {
+/**
+ * Display a message with a dismiss putton, and optionally a second button.
+ */
+public class AlertDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
+
+ public interface OnClickListener {
+ /**
+ * This method will be invoked when a button in the dialog is clicked.
+ *
+ * @param fragment the AlertDialogFragment that received the click
+ * @param which the button that was clicked (e.g.
+ * {@link DialogInterface#BUTTON_POSITIVE}) or the position
+ * of the item clicked
+ */
+ public void onClick(AlertDialogFragment fragment, int which);
+ }
private static final String NAME = AlertDialogFragment.class.getName();
private static final String KEY_MESSAGE = NAME + "_message";
private static final String KEY_BUTTON_NEGATIVE = NAME + "_button_negative";
+ private static final String KEY_BUTTON_POSITIVE = NAME + "_button_positive";
- public static void showMessageDialog(Activity activity, CharSequence message) {
+ /**
+ * Create and show a DialogFragment with the given message.
+ * @param activity originating Activity
+ * @param message displayed message
+ * @param positiveButtonLabel label for second button, if any. If non-null, activity must
+ * implement AlertDialogFragment.OnClickListener to respond.
+ */
+ public static void showMessageDialog(Activity activity, CharSequence message,
+ @Nullable CharSequence positiveButtonLabel) {
+ final AlertDialogFragment dialogFragment = new AlertDialogFragment();
final Bundle args = new Bundle();
args.putCharSequence(KEY_MESSAGE, message);
args.putCharSequence(KEY_BUTTON_NEGATIVE, activity.getString(R.string.dismiss));
-
- final AlertDialogFragment dialogFragment = new AlertDialogFragment();
+ if (positiveButtonLabel != null) {
+ args.putCharSequence(KEY_BUTTON_POSITIVE, positiveButtonLabel);
+ }
dialogFragment.setArguments(args);
dialogFragment.show(activity.getFragmentManager(), null /* tag */);
}
@@ -53,9 +81,21 @@ public class AlertDialogFragment extends DialogFragment {
final TextView textView = (TextView) inflater.inflate(R.layout.dialog_message,
null /* root */);
textView.setText(args.getCharSequence(KEY_MESSAGE));
- return new AlertDialog.Builder(context)
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setView(textView)
- .setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */)
- .create();
+ .setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */);
+ final CharSequence positiveButtonLabel = args.getCharSequence(KEY_BUTTON_POSITIVE);
+ if (positiveButtonLabel != null) {
+ builder.setPositiveButton(positiveButtonLabel, this);
+ }
+ return builder.create();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Activity activity = getActivity();
+ if (activity instanceof AlertDialogFragment.OnClickListener /* always true */) {
+ ((AlertDialogFragment.OnClickListener) activity).onClick(this, which);
+ }
}
}
diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java
index 2b3d1ed..dc132f6 100644
--- a/src/com/android/calculator2/BoundedRational.java
+++ b/src/com/android/calculator2/BoundedRational.java
@@ -16,22 +16,25 @@
package com.android.calculator2;
-// We implement rational numbers of bounded size.
-// If the length of the nuumerator plus the length of the denominator
-// exceeds a maximum size, we simply return null, and rely on our caller
-// do something else.
-// We currently never return null for a pure integer.
-// TODO: Reconsider that. With some care, large factorials might
-// become much faster.
-//
-// We also implement a number of irrational functions. These return
-// a non-null result only when the result is known to be rational.
import java.math.BigInteger;
import com.hp.creals.CR;
+/**
+ * Rational numbers that may turn to null if they get too big.
+ * For many operations, if the length of the nuumerator plus the length of the denominator exceeds
+ * a maximum size, we simply return null, and rely on our caller do something else.
+ * We currently never return null for a pure integer or for a BoundedRational that has just been
+ * constructed.
+ *
+ * We also implement a number of irrational functions. These return a non-null result only when
+ * the result is known to be rational.
+ */
public class BoundedRational {
+ // TODO: Consider returning null for integers. With some care, large factorials might become
+ // much faster.
// TODO: Maybe eventually make this extend Number?
+
private static final int MAX_SIZE = 800; // total, in bits
private final BigInteger mNum;
@@ -57,13 +60,19 @@ public class BoundedRational {
mDen = BigInteger.valueOf(1);
}
- // Debug or log messages only, not pretty.
+ /**
+ * Convert to String reflecting raw representation.
+ * Debug or log messages only, not pretty.
+ */
public String toString() {
return mNum.toString() + "/" + mDen.toString();
}
- // Output to user, more expensive, less useful for debugging
- // Not internationalized.
+ /**
+ * Convert to readable String.
+ * Intended for output output to user. More expensive, less useful for debugging than
+ * toString(). Not internationalized.
+ */
public String toNiceString() {
BoundedRational nicer = reduce().positiveDen();
String result = nicer.mNum.toString();
@@ -74,11 +83,16 @@ public class BoundedRational {
}
public static String toString(BoundedRational r) {
- if (r == null) return "not a small rational";
+ if (r == null) {
+ return "not a small rational";
+ }
return r.toString();
}
- // Primarily for debugging; clearly not exact
+ /**
+ * Return a double approximation.
+ * Primarily for debugging.
+ */
public double doubleValue() {
return mNum.doubleValue() / mDen.doubleValue();
}
@@ -93,39 +107,55 @@ public class BoundedRational {
}
private boolean tooBig() {
- if (mDen.equals(BigInteger.ONE)) return false;
+ if (mDen.equals(BigInteger.ONE)) {
+ return false;
+ }
return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE);
}
- // return an equivalent fraction with a positive denominator.
+ /**
+ * Return an equivalent fraction with a positive denominator.
+ */
private BoundedRational positiveDen() {
- if (mDen.compareTo(BigInteger.ZERO) > 0) return this;
+ if (mDen.signum() > 0) {
+ return this;
+ }
return new BoundedRational(mNum.negate(), mDen.negate());
}
- // Return an equivalent fraction in lowest terms.
+ /**
+ * Return an equivalent fraction in lowest terms.
+ * Denominator sign may remain negative.
+ */
private BoundedRational reduce() {
- if (mDen.equals(BigInteger.ONE)) return this; // Optimization only
- BigInteger divisor = mNum.gcd(mDen);
+ if (mDen.equals(BigInteger.ONE)) {
+ return this; // Optimization only
+ }
+ final BigInteger divisor = mNum.gcd(mDen);
return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
}
- // Return a possibly reduced version of this that's not tooBig.
- // Return null if none exists.
+ /**
+ * Return a possibly reduced version of this that's not tooBig().
+ * Return null if none exists.
+ */
private BoundedRational maybeReduce() {
- if (!tooBig()) return this;
+ if (!tooBig()) {
+ return this;
+ }
BoundedRational result = positiveDen();
- if (!result.tooBig()) return this;
result = result.reduce();
- if (!result.tooBig()) return this;
+ if (!result.tooBig()) {
+ return this;
+ }
return null;
}
public int compareTo(BoundedRational r) {
- // Compare by multiplying both sides by denominators,
- // invert result if denominator product was negative.
- return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen))
- * mDen.signum() * r.mDen.signum();
+ // Compare by multiplying both sides by denominators, invert result if denominator product
+ // was negative.
+ return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen)) * mDen.signum()
+ * r.mDen.signum();
}
public int signum() {
@@ -136,28 +166,37 @@ public class BoundedRational {
return compareTo(r) == 0;
}
- // We use static methods for arithmetic, so that we can
- // easily handle the null case.
- // We try to catch domain errors whenever possible, sometimes even when
- // one of the arguments is null, but not relevant.
+ // We use static methods for arithmetic, so that we can easily handle the null case. We try
+ // to catch domain errors whenever possible, sometimes even when one of the arguments is null,
+ // but not relevant.
- // Returns equivalent BigInteger result if it exists, null if not.
+ /**
+ * Returns equivalent BigInteger result if it exists, null if not.
+ */
public static BigInteger asBigInteger(BoundedRational r) {
- if (r == null) return null;
- if (!r.mDen.equals(BigInteger.ONE)) r = r.reduce();
- if (!r.mDen.equals(BigInteger.ONE)) return null;
- return r.mNum;
+ if (r == null) {
+ return null;
+ }
+ final BigInteger[] quotAndRem = r.mNum.divideAndRemainder(r.mDen);
+ if (quotAndRem[1].signum() == 0) {
+ return quotAndRem[0];
+ } else {
+ return null;
+ }
}
public static BoundedRational add(BoundedRational r1, BoundedRational r2) {
- if (r1 == null || r2 == null) return null;
+ if (r1 == null || r2 == null) {
+ return null;
+ }
final BigInteger den = r1.mDen.multiply(r2.mDen);
- final BigInteger num = r1.mNum.multiply(r2.mDen)
- .add(r2.mNum.multiply(r1.mDen));
+ final BigInteger num = r1.mNum.multiply(r2.mDen).add(r2.mNum.multiply(r1.mDen));
return new BoundedRational(num,den).maybeReduce();
}
public static BoundedRational negate(BoundedRational r) {
- if (r == null) return null;
+ if (r == null) {
+ return null;
+ }
return new BoundedRational(r.mNum.negate(), r.mDen);
}
@@ -166,10 +205,11 @@ public class BoundedRational {
}
static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
- // It's tempting but marginally unsound to reduce 0 * null to zero.
- // The null could represent an infinite value, for which we
- // failed to throw an exception because it was too big.
- if (r1 == null || r2 == null) return null;
+ // It's tempting but marginally unsound to reduce 0 * null to 0. The null could represent
+ // an infinite value, for which we failed to throw an exception because it was too big.
+ if (r1 == null || r2 == null) {
+ return null;
+ }
final BigInteger num = r1.mNum.multiply(r2.mNum);
final BigInteger den = r1.mDen.multiply(r2.mDen);
return new BoundedRational(num,den).maybeReduce();
@@ -181,9 +221,14 @@ public class BoundedRational {
}
}
+ /**
+ * Return the reciprocal of r (or null).
+ */
static BoundedRational inverse(BoundedRational r) {
- if (r == null) return null;
- if (r.mNum.equals(BigInteger.ZERO)) {
+ if (r == null) {
+ return null;
+ }
+ if (r.mNum.signum() == 0) {
throw new ZeroDivisionException();
}
return new BoundedRational(r.mDen, r.mNum);
@@ -194,19 +239,22 @@ public class BoundedRational {
}
static BoundedRational sqrt(BoundedRational r) {
- // Return non-null if numerator and denominator are small perfect
- // squares.
- if (r == null) return null;
+ // Return non-null if numerator and denominator are small perfect squares.
+ if (r == null) {
+ return null;
+ }
r = r.positiveDen().reduce();
- if (r.mNum.compareTo(BigInteger.ZERO) < 0) {
+ if (r.mNum.signum() < 0) {
throw new ArithmeticException("sqrt(negative)");
}
- final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
- r.mNum.doubleValue())));
- if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) return null;
- final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
- r.mDen.doubleValue())));
- if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) return null;
+ final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mNum.doubleValue())));
+ if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) {
+ return null;
+ }
+ final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mDen.doubleValue())));
+ if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) {
+ return null;
+ }
return new BoundedRational(num_sqrt, den_sqrt);
}
@@ -220,39 +268,45 @@ public class BoundedRational {
public final static BoundedRational THIRTY = new BoundedRational(30);
public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
- public final static BoundedRational MINUS_FORTY_FIVE =
- new BoundedRational(-45);
+ public final static BoundedRational MINUS_FORTY_FIVE = new BoundedRational(-45);
public final static BoundedRational NINETY = new BoundedRational(90);
public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
private static BoundedRational map0to0(BoundedRational r) {
- if (r == null) return null;
- if (r.mNum.equals(BigInteger.ZERO)) {
+ if (r == null) {
+ return null;
+ }
+ if (r.mNum.signum() == 0) {
return ZERO;
}
return null;
}
private static BoundedRational map0to1(BoundedRational r) {
- if (r == null) return null;
- if (r.mNum.equals(BigInteger.ZERO)) {
+ if (r == null) {
+ return null;
+ }
+ if (r.mNum.signum() == 0) {
return ONE;
}
return null;
}
private static BoundedRational map1to0(BoundedRational r) {
- if (r == null) return null;
+ if (r == null) {
+ return null;
+ }
if (r.mNum.equals(r.mDen)) {
return ZERO;
}
return null;
}
- // Throw an exception if the argument is definitely out of bounds for asin
- // or acos.
+ // Throw an exception if the argument is definitely out of bounds for asin or acos.
private static void checkAsinDomain(BoundedRational r) {
- if (r == null) return;
+ if (r == null) {
+ return;
+ }
if (r.mNum.abs().compareTo(r.mDen.abs()) > 0) {
throw new ArithmeticException("inverse trig argument out of range");
}
@@ -266,9 +320,13 @@ public class BoundedRational {
public static BoundedRational degreeSin(BoundedRational r) {
final BigInteger r_BI = asBigInteger(r);
- if (r_BI == null) return null;
+ if (r_BI == null) {
+ return null;
+ }
final int r_int = r_BI.mod(BIG360).intValue();
- if (r_int % 30 != 0) return null;
+ if (r_int % 30 != 0) {
+ return null;
+ }
switch (r_int / 10) {
case 0:
return ZERO;
@@ -299,10 +357,12 @@ public class BoundedRational {
public static BoundedRational degreeAsin(BoundedRational r) {
checkAsinDomain(r);
final BigInteger r2_BI = asBigInteger(multiply(r, TWO));
- if (r2_BI == null) return null;
+ if (r2_BI == null) {
+ return null;
+ }
final int r2_int = r2_BI.intValue();
- // Somewhat surprisingly, it seems to be the case that the following
- // covers all rational cases:
+ // Somewhat surprisingly, it seems to be the case that the following covers all rational
+ // cases:
switch (r2_int) {
case -2: // Corresponding to -1 argument
return MINUS_NINETY;
@@ -320,18 +380,18 @@ public class BoundedRational {
}
public static BoundedRational tan(BoundedRational r) {
- // Unlike the degree case, we cannot check for the singularity,
- // since it occurs at an irrational argument.
+ // Unlike the degree case, we cannot check for the singularity, since it occurs at an
+ // irrational argument.
return map0to0(r);
}
public static BoundedRational degreeTan(BoundedRational r) {
- final BoundedRational degree_sin = degreeSin(r);
- final BoundedRational degree_cos = degreeCos(r);
- if (degree_cos != null && degree_cos.mNum.equals(BigInteger.ZERO)) {
+ final BoundedRational degSin = degreeSin(r);
+ final BoundedRational degCos = degreeCos(r);
+ if (degCos != null && degCos.mNum.signum() == 0) {
throw new ArithmeticException("Tangent undefined");
}
- return divide(degree_sin, degree_cos);
+ return divide(degSin, degCos);
}
public static BoundedRational atan(BoundedRational r) {
@@ -340,8 +400,12 @@ public class BoundedRational {
public static BoundedRational degreeAtan(BoundedRational r) {
final BigInteger r_BI = asBigInteger(r);
- if (r_BI == null) return null;
- if (r_BI.abs().compareTo(BigInteger.ONE) > 0) return null;
+ if (r_BI == null) {
+ return null;
+ }
+ if (r_BI.abs().compareTo(BigInteger.ONE) > 0) {
+ return null;
+ }
final int r_int = r_BI.intValue();
// Again, these seem to be all rational cases:
switch (r_int) {
@@ -376,16 +440,20 @@ public class BoundedRational {
private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
- // Compute an integral power of this
+ /**
+ * Compute an integral power of this.
+ */
private BoundedRational pow(BigInteger exp) {
- if (exp.compareTo(BigInteger.ZERO) < 0) {
+ if (exp.signum() < 0) {
return inverse(pow(exp.negate()));
}
- if (exp.equals(BigInteger.ONE)) return this;
+ if (exp.equals(BigInteger.ONE)) {
+ return this;
+ }
if (exp.and(BigInteger.ONE).intValue() == 1) {
return multiply(pow(exp.subtract(BigInteger.ONE)), this);
}
- if (exp.equals(BigInteger.ZERO)) {
+ if (exp.signum() == 0) {
return ONE;
}
BoundedRational tmp = pow(exp.shiftRight(1));
@@ -396,13 +464,21 @@ public class BoundedRational {
}
public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
- if (exp == null) return null;
- if (exp.mNum.equals(BigInteger.ZERO)) {
+ if (exp == null) {
+ return null;
+ }
+ if (exp.mNum.signum() == 0) {
+ // Questionable if base has undefined value. Java.lang.Math.pow() returns 1 anyway,
+ // so we do the same.
return new BoundedRational(1);
}
- if (base == null) return null;
+ if (base == null) {
+ return null;
+ }
exp = exp.reduce().positiveDen();
- if (!exp.mDen.equals(BigInteger.ONE)) return null;
+ if (!exp.mDen.equals(BigInteger.ONE)) {
+ return null;
+ }
return base.pow(exp.mNum);
}
@@ -417,12 +493,14 @@ public class BoundedRational {
return map0to1(r);
}
- // Return the base 10 log of n, if n is a power of 10, -1 otherwise.
- // n must be positive.
+ /**
+ * Return the base 10 log of n, if n is a power of 10, -1 otherwise.
+ * n must be positive.
+ */
private static long b10Log(BigInteger n) {
// This algorithm is very naive, but we doubt it matters.
long count = 0;
- while (n.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
+ while (n.mod(BigInteger.TEN).signum() == 0) {
if (Thread.interrupted()) {
throw new CR.AbortedException();
}
@@ -436,26 +514,35 @@ public class BoundedRational {
}
public static BoundedRational log(BoundedRational r) {
- if (r == null) return null;
+ if (r == null) {
+ return null;
+ }
if (r.signum() <= 0) {
throw new ArithmeticException("log(non-positive)");
}
r = r.reduce().positiveDen();
- if (r == null) return null;
+ if (r == null) {
+ return null;
+ }
if (r.mDen.equals(BigInteger.ONE)) {
long log = b10Log(r.mNum);
- if (log != -1) return new BoundedRational(log);
+ if (log != -1) {
+ return new BoundedRational(log);
+ }
} else if (r.mNum.equals(BigInteger.ONE)) {
long log = b10Log(r.mDen);
- if (log != -1) return new BoundedRational(-log);
+ if (log != -1) {
+ return new BoundedRational(-log);
+ }
}
return null;
}
- // Generalized factorial.
- // Compute n * (n - step) * (n - 2 * step) * ...
- // This can be used to compute factorial a bit faster, especially
- // if BigInteger uses sub-quadratic multiplication.
+ /**
+ * Generalized factorial.
+ * Compute n * (n - step) * (n - 2 * step) * etc. This can be used to compute factorial a bit
+ * faster, especially if BigInteger uses sub-quadratic multiplication.
+ */
private static BigInteger genFactorial(long n, long step) {
if (n > 4 * step) {
BigInteger prod1 = genFactorial(n, 2 * step);
@@ -468,6 +555,9 @@ public class BoundedRational {
}
return prod1.multiply(prod2);
} else {
+ if (n == 0) {
+ return BigInteger.ONE;
+ }
BigInteger res = BigInteger.valueOf(n);
for (long i = n - step; i > 1; i -= step) {
res = res.multiply(BigInteger.valueOf(i));
@@ -476,61 +566,68 @@ public class BoundedRational {
}
}
- // Factorial;
- // always produces non-null (or exception) when called on non-null r.
+ /**
+ * Factorial function.
+ * Always produces non-null (or exception) when called on non-null r.
+ */
public static BoundedRational fact(BoundedRational r) {
- if (r == null) return null; // Caller should probably preclude this case.
- final BigInteger r_BI = asBigInteger(r);
- if (r_BI == null) {
+ if (r == null) {
+ return null;
+ }
+ final BigInteger rAsInt = asBigInteger(r);
+ if (rAsInt == null) {
throw new ArithmeticException("Non-integral factorial argument");
}
- if (r_BI.signum() < 0) {
+ if (rAsInt.signum() < 0) {
throw new ArithmeticException("Negative factorial argument");
}
- if (r_BI.bitLength() > 30) {
+ if (rAsInt.bitLength() > 30) {
// Will fail. LongValue() may not work. Punt now.
throw new ArithmeticException("Factorial argument too big");
}
- return new BoundedRational(genFactorial(r_BI.longValue(), 1));
+ return new BoundedRational(genFactorial(rAsInt.longValue(), 1));
}
private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1);
- // Return the number of decimal digits to the right of the
- // decimal point required to represent the argument exactly,
- // or Integer.MAX_VALUE if it's not possible.
- // Never returns a value les than zero, even if r is
- // a power of ten.
+ /**
+ * Return the number of decimal digits to the right of the decimal point required to represent
+ * the argument exactly.
+ * Return Integer.MAX_VALUE if that's not possible. Never returns a value less than zero, even
+ * if r is a power of ten.
+ */
static int digitsRequired(BoundedRational r) {
- if (r == null) return Integer.MAX_VALUE;
- int powers_of_two = 0; // Max power of 2 that divides denominator
- int powers_of_five = 0; // Max power of 5 that divides denominator
+ if (r == null) {
+ return Integer.MAX_VALUE;
+ }
+ int powersOfTwo = 0; // Max power of 2 that divides denominator
+ int powersOfFive = 0; // Max power of 5 that divides denominator
// Try the easy case first to speed things up.
- if (r.mDen.equals(BigInteger.ONE)) return 0;
+ if (r.mDen.equals(BigInteger.ONE)) {
+ return 0;
+ }
r = r.reduce();
BigInteger den = r.mDen;
if (den.bitLength() > MAX_SIZE) {
return Integer.MAX_VALUE;
}
while (!den.testBit(0)) {
- ++powers_of_two;
+ ++powersOfTwo;
den = den.shiftRight(1);
}
- while (den.mod(BIG_FIVE).equals(BigInteger.ZERO)) {
- ++powers_of_five;
+ while (den.mod(BIG_FIVE).signum() == 0) {
+ ++powersOfFive;
den = den.divide(BIG_FIVE);
}
- // If the denominator has a factor of other than 2 or 5
- // (the divisors of 10), the decimal expansion does not
- // terminate. Multiplying the fraction by any number of
- // powers of 10 will not cancel the demoniator.
- // (Recall the fraction was in lowest terms to start with.)
- // Otherwise the powers of 10 we need to cancel the denominator
- // is the larger of powers_of_two and powers_of_five.
+ // If the denominator has a factor of other than 2 or 5 (the divisors of 10), the decimal
+ // expansion does not terminate. Multiplying the fraction by any number of powers of 10
+ // will not cancel the demoniator. (Recall the fraction was in lowest terms to start
+ // with.) Otherwise the powers of 10 we need to cancel the denominator is the larger of
+ // powersOfTwo and powersOfFive.
if (!den.equals(BigInteger.ONE) && !den.equals(BIG_MINUS_ONE)) {
return Integer.MAX_VALUE;
}
- return Math.max(powers_of_two, powers_of_five);
+ return Math.max(powersOfTwo, powersOfFive);
}
}
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 4eecb50..84f92c8 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -32,7 +32,9 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.app.Activity;
+import android.app.AlertDialog;
import android.content.ClipData;
+import android.content.DialogInterface;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
@@ -71,7 +73,8 @@ import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class Calculator extends Activity
- implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener {
+ implements OnTextSizeChangeListener, OnLongClickListener, CalculatorText.OnPasteListener,
+ AlertDialogFragment.OnClickListener {
/**
* Constant for an invalid resource id.
@@ -445,19 +448,27 @@ public class Calculator extends Activity
}
}
+ /**
+ * Switch to INPUT from RESULT state in response to input of the specified button_id.
+ * View.NO_ID is treated as an incomplete function id.
+ */
+ private void switchToInput(int button_id) {
+ if (KeyMaps.isBinary(button_id) || KeyMaps.isSuffix(button_id)) {
+ mEvaluator.collapse();
+ } else {
+ announceClearedForAccessibility();
+ mEvaluator.clear();
+ }
+ setState(CalculatorState.INPUT);
+ }
+
// Add the given button id to input expression.
// If appropriate, clear the expression before doing so.
private void addKeyToExpr(int id) {
if (mCurrentState == CalculatorState.ERROR) {
setState(CalculatorState.INPUT);
} else if (mCurrentState == CalculatorState.RESULT) {
- if (KeyMaps.isBinary(id) || KeyMaps.isSuffix(id)) {
- mEvaluator.collapse();
- } else {
- announceClearForAccessibility();
- mEvaluator.clear();
- }
- setState(CalculatorState.INPUT);
+ switchToInput(id);
}
if (!mEvaluator.append(id)) {
// TODO: Some user visible feedback?
@@ -653,6 +664,11 @@ public class Calculator extends Activity
} else {
mEvaluator.delete();
}
+ if (mEvaluator.getExpr().isEmpty()
+ && (mUnprocessedChars == null || mUnprocessedChars.isEmpty())) {
+ // Resulting formula won't be announced, since it's empty.
+ announceClearedForAccessibility();
+ }
redisplayAfterFormulaChange();
}
@@ -710,8 +726,8 @@ public class Calculator extends Activity
animatorSet.start();
}
- private void announceClearForAccessibility() {
- mResultText.announceForAccessibility(getResources().getString(R.string.desc_clr));
+ private void announceClearedForAccessibility() {
+ mResultText.announceForAccessibility(getResources().getString(R.string.cleared));
}
private void onClear() {
@@ -719,7 +735,7 @@ public class Calculator extends Activity
return;
}
cancelIfEvaluating(true);
- announceClearForAccessibility();
+ announceClearedForAccessibility();
reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -834,7 +850,15 @@ public class Calculator extends Activity
mFormulaText.setTranslationY(0.0f);
mFormulaText.requestFocus();
- }
+ }
+
+ @Override
+ public void onClick(AlertDialogFragment fragment, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Timeout extension request.
+ mEvaluator.setLongTimeOut();
+ }
+ }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
@@ -876,7 +900,7 @@ public class Calculator extends Activity
}
private void displayMessage(String s) {
- AlertDialogFragment.showMessageDialog(this, s);
+ AlertDialogFragment.showMessageDialog(this, s, null);
}
private void displayFraction() {
@@ -911,6 +935,10 @@ public class Calculator extends Activity
int current = 0;
int len = moreChars.length();
boolean lastWasDigit = false;
+ if (mCurrentState == CalculatorState.RESULT && len != 0) {
+ // Clear display immediately for incomplete function name.
+ switchToInput(KeyMaps.keyForChar(moreChars.charAt(current)));
+ }
while (current < len) {
char c = moreChars.charAt(current);
int k = KeyMaps.keyForChar(c);
@@ -991,7 +1019,7 @@ public class Calculator extends Activity
setState(CalculatorState.INPUT);
mEvaluator.clear();
}
- mEvaluator.addSaved();
+ mEvaluator.appendSaved();
redisplayAfterFormulaChange();
} else {
addChars(item.coerceToText(this).toString(), false);
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index 8a008b8..14d9236 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -36,9 +36,19 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
-// A mathematical expression represented as a sequence of "tokens".
-// Many tokes are represented by button ids for the corresponding operator.
-// Parsed only when we evaluate the expression using the "eval" method.
+/**
+ * A mathematical expression represented as a sequence of "tokens".
+ * Many tokens are represented by button ids for the corresponding operator.
+ * A token may also represent the result of a previously evaluated expression.
+ * The add() method adds a token to the end of the expression. The delete method() removes one.
+ * Clear() deletes the entire expression contents. Eval() evaluates the expression,
+ * producing both a constructive real (CR), and possibly a BoundedRational result.
+ * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
+ *
+ * The write() method is used to save the current expression. Note that CR provides no
+ * serialization facility. Thus we save all previously computed values by writing out the
+ * expression that was used to compute them, and reevaluate on input.
+ */
class CalculatorExpr {
private ArrayList<Token> mExpr; // The actual representation
// as a list of tokens. Constant
@@ -66,41 +76,45 @@ class CalculatorExpr {
abstract CharSequence toCharSequence(Context context);
}
- // An operator token
+ /**
+ * Representation of an operator token
+ */
private static class Operator extends Token {
- final int mId; // We use the button resource id
+ public final int id; // We use the button resource id
Operator(int resId) {
- mId = resId;
+ id = resId;
}
Operator(DataInput in) throws IOException {
- mId = in.readInt();
+ id = in.readInt();
}
@Override
void write(DataOutput out) throws IOException {
out.writeByte(TokenKind.OPERATOR.ordinal());
- out.writeInt(mId);
+ out.writeInt(id);
}
@Override
public CharSequence toCharSequence(Context context) {
- String desc = KeyMaps.toDescriptiveString(context, mId);
+ String desc = KeyMaps.toDescriptiveString(context, id);
if (desc != null) {
- SpannableString result = new SpannableString(KeyMaps.toString(context, mId));
+ SpannableString result = new SpannableString(KeyMaps.toString(context, id));
Object descSpan = new TtsSpan.TextBuilder(desc).build();
result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return result;
} else {
- return KeyMaps.toString(context, mId);
+ return KeyMaps.toString(context, id);
}
}
@Override
TokenKind kind() { return TokenKind.OPERATOR; }
}
- // A (possibly incomplete) numerical constant.
- // Supports addition and removal of trailing characters; hence mutable.
+ /**
+ * Representation of a (possibly incomplete) numerical constant.
+ * Supports addition and removal of trailing characters; hence mutable.
+ */
private static class Constant extends Token implements Cloneable {
private boolean mSawDecimal;
- String mWhole; // String preceding decimal point.
+ private String mWhole; // String preceding decimal point.
private String mFraction; // String after decimal point.
private int mExponent; // Explicit exponent, only generated through addExponent.
@@ -132,7 +146,7 @@ class CalculatorExpr {
// Just return false if this was the second (or later) decimal point
// in this constant.
// Assumes that this constant does not have an exponent.
- boolean add(int id) {
+ public boolean add(int id) {
if (id == R.id.dec_point) {
if (mSawDecimal || mExponent != 0) return false;
mSawDecimal = true;
@@ -159,14 +173,16 @@ class CalculatorExpr {
return true;
}
- void addExponent(int exp) {
+ public void addExponent(int exp) {
// Note that adding a 0 exponent is a no-op. That's OK.
mExponent = exp;
}
- // Undo the last add.
- // Assumes the constant is nonempty.
- void delete() {
+ /**
+ * Undo the last add or remove last exponent digit.
+ * Assumes the constant is nonempty.
+ */
+ public void delete() {
if (mExponent != 0) {
mExponent /= 10;
// Once zero, it can only be added back with addExponent.
@@ -179,12 +195,14 @@ class CalculatorExpr {
}
}
- boolean isEmpty() {
+ public boolean isEmpty() {
return (mSawDecimal == false && mWhole.isEmpty());
}
- // Produces human-readable string, as typed.
- // Result is internationalized.
+ /**
+ * Produce human-readable string representation of constant, as typed.
+ * Result is internationalized.
+ */
@Override
public String toString() {
String result = mWhole;
@@ -198,10 +216,20 @@ class CalculatorExpr {
return KeyMaps.translateResult(result);
}
- // Return non-null BoundedRational representation.
- public BoundedRational toRational() {
+ /**
+ * Return BoundedRational representation of constant, if well-formed.
+ * Result is never null.
+ */
+ public BoundedRational toRational() throws SyntaxException {
String whole = mWhole;
- if (whole.isEmpty()) whole = "0";
+ if (whole.isEmpty()) {
+ if (mFraction.isEmpty()) {
+ // Decimal point without digits.
+ throw new SyntaxException();
+ } else {
+ whole = "0";
+ }
+ }
BigInteger num = new BigInteger(whole + mFraction);
BigInteger den = BigInteger.TEN.pow(mFraction.length());
if (mExponent > 0) {
@@ -214,92 +242,94 @@ class CalculatorExpr {
}
@Override
- CharSequence toCharSequence(Context context) {
+ public CharSequence toCharSequence(Context context) {
return toString();
}
@Override
- TokenKind kind() { return TokenKind.CONSTANT; }
+ public TokenKind kind() {
+ return TokenKind.CONSTANT;
+ }
// Override clone to make it public
@Override
public Object clone() {
- Constant res = new Constant();
- res.mWhole = mWhole;
- res.mFraction = mFraction;
- res.mSawDecimal = mSawDecimal;
- res.mExponent = mExponent;
- return res;
+ Constant result = new Constant();
+ result.mWhole = mWhole;
+ result.mFraction = mFraction;
+ result.mSawDecimal = mSawDecimal;
+ result.mExponent = mExponent;
+ return result;
}
}
- // Hash maps used to detect duplicate subexpressions when
- // we write out CalculatorExprs and read them back in.
+ // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
+ // read them back in.
private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
- new ThreadLocal<IdentityHashMap<CR,Integer>>();
+ new ThreadLocal<IdentityHashMap<CR,Integer>>();
// Maps expressions to indices on output
private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
- new ThreadLocal<HashMap<Integer,PreEval>>();
+ new ThreadLocal<HashMap<Integer,PreEval>>();
// Maps expressions to indices on output
- private static final ThreadLocal<Integer> exprIndex =
- new ThreadLocal<Integer>();
+ private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
- static void initExprOutput() {
+ /**
+ * Prepare for expression output.
+ * Initializes map that will lbe used to avoid duplicating shared subexpressions.
+ * This avoids a potential exponential blow-up in the expression size.
+ */
+ public static void initExprOutput() {
outMap.set(new IdentityHashMap<CR,Integer>());
exprIndex.set(Integer.valueOf(0));
}
- static void initExprInput() {
+ /**
+ * Prepare for expression input.
+ * Initializes map that will be used to reconstruct shared subexpressions.
+ */
+ public static void initExprInput() {
inMap.set(new HashMap<Integer,PreEval>());
}
- // We treat previously evaluated subexpressions as tokens
- // These are inserted when either:
- // - We continue an expression after evaluating some of it.
- // - TODO: When we copy/paste expressions.
- // The representation includes three different representations
- // of the expression:
- // 1) The CR value for use in computation.
- // 2) The integer value for use in the computations,
- // if the expression evaluates to an integer.
- // 3a) The corresponding CalculatorExpr, together with
- // 3b) The context (currently just deg/rad mode) used to evaluate
- // the expression.
- // 4) A short string representation that is used to
- // Display the expression.
- //
- // (3) is present only so that we can persist the object.
- // (4) is stored explicitly to avoid waiting for recomputation in the UI
- // thread.
+ /**
+ * The "token" class for previously evaluated subexpressions.
+ * We treat previously evaluated subexpressions as tokens. These are inserted when we either
+ * continue an expression after evaluating some of it, or copy an expression and paste it back
+ * in.
+ * The representation includes both CR and possibly BoundedRational values. In order to
+ * support saving and restoring, we also include the underlying expression itself, and the
+ * context (currently just degree mode) used to evaluate it. The short string representation
+ * is also stored in order to avoid potentially expensive recomputation in the UI thread.
+ */
private static class PreEval extends Token {
- final CR mValue;
- final BoundedRational mRatValue;
+ public final CR value;
+ public final BoundedRational ratValue;
private final CalculatorExpr mExpr;
private final EvalContext mContext;
private final String mShortRep; // Not internationalized.
PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
EvalContext ec, String shortRep) {
- mValue = val;
- mRatValue = ratVal;
+ value = val;
+ ratValue = ratVal;
mExpr = expr;
mContext = ec;
mShortRep = shortRep;
}
// In writing out PreEvals, we are careful to avoid writing
// out duplicates. We assume that two expressions are
- // duplicates if they have the same mVal. This avoids a
+ // duplicates if they have the same CR value. This avoids a
// potential exponential blow up in certain off cases and
// redundant evaluation after reading them back in.
// The parameter hash map maps expressions we've seen
// before to their index.
@Override
- void write(DataOutput out) throws IOException {
+ public void write(DataOutput out) throws IOException {
out.writeByte(TokenKind.PRE_EVAL.ordinal());
- Integer index = outMap.get().get(mValue);
+ Integer index = outMap.get().get(value);
if (index == null) {
int nextIndex = exprIndex.get() + 1;
exprIndex.set(nextIndex);
- outMap.get().put(mValue, nextIndex);
+ outMap.get().put(value, nextIndex);
out.writeInt(nextIndex);
mExpr.write(out);
mContext.write(out);
@@ -315,13 +345,10 @@ class CalculatorExpr {
if (prev == null) {
mExpr = new CalculatorExpr(in);
mContext = new EvalContext(in, mExpr.mExpr.size());
- // Recompute other fields
- // We currently do this in the UI thread, but we
- // only create PreEval expressions that were
- // previously successfully evaluated, and thus
- // don't diverge. We also only evaluate to a
- // constructive real, which involves substantial
- // work only in fairly contrived circumstances.
+ // Recompute other fields We currently do this in the UI thread, but we only
+ // create PreEval expressions that were previously successfully evaluated, and
+ // thus don't diverge. We also only evaluate to a constructive real, which
+ // involves substantial work only in fairly contrived circumstances.
// TODO: Deal better with slow evaluations.
EvalRet res = null;
try {
@@ -331,32 +358,35 @@ class CalculatorExpr {
// expressions that can be evaluated.
Log.e("Calculator", "Unexpected syntax exception" + e);
}
- mValue = res.mVal;
- mRatValue = res.mRatVal;
+ value = res.val;
+ ratValue = res.ratVal;
mShortRep = in.readUTF();
inMap.get().put(index, this);
} else {
- mValue = prev.mValue;
- mRatValue = prev.mRatValue;
+ value = prev.value;
+ ratValue = prev.ratValue;
mExpr = prev.mExpr;
mContext = prev.mContext;
mShortRep = prev.mShortRep;
}
}
@Override
- CharSequence toCharSequence(Context context) {
+ public CharSequence toCharSequence(Context context) {
return KeyMaps.translateResult(mShortRep);
}
@Override
- TokenKind kind() {
+ public TokenKind kind() {
return TokenKind.PRE_EVAL;
}
- boolean hasEllipsis() {
+ public boolean hasEllipsis() {
return mShortRep.lastIndexOf(KeyMaps.ELLIPSIS) != -1;
}
}
- static Token newToken(DataInput in) throws IOException {
+ /**
+ * Read token from in.
+ */
+ public static Token newToken(DataInput in) throws IOException {
TokenKind kind = tokenKindValues[in.readByte()];
switch(kind) {
case CONSTANT:
@@ -377,6 +407,9 @@ class CalculatorExpr {
mExpr = expr;
}
+ /**
+ * Construct CalculatorExpr, by reading it from in.
+ */
CalculatorExpr(DataInput in) throws IOException {
mExpr = new ArrayList<Token>();
int size = in.readInt();
@@ -385,7 +418,10 @@ class CalculatorExpr {
}
}
- void write(DataOutput out) throws IOException {
+ /**
+ * Write this expression to out.
+ */
+ public void write(DataOutput out) throws IOException {
int size = mExpr.size();
out.writeInt(size);
for (int i = 0; i < size; ++i) {
@@ -393,6 +429,10 @@ class CalculatorExpr {
}
}
+ /**
+ * Does this expression end with a numeric constant?
+ * As opposed to an operator or preevaluated expression.
+ */
boolean hasTrailingConstant() {
int s = mExpr.size();
if (s == 0) {
@@ -402,13 +442,16 @@ class CalculatorExpr {
return t instanceof Constant;
}
+ /**
+ * Does this expression end with a binary operator?
+ */
private boolean hasTrailingBinary() {
int s = mExpr.size();
if (s == 0) return false;
Token t = mExpr.get(s-1);
if (!(t instanceof Operator)) return false;
Operator o = (Operator)t;
- return (KeyMaps.isBinary(o.mId));
+ return (KeyMaps.isBinary(o.id));
}
/**
@@ -420,10 +463,10 @@ class CalculatorExpr {
*/
boolean add(int id) {
int s = mExpr.size();
- int d = KeyMaps.digVal(id);
- boolean binary = KeyMaps.isBinary(id);
+ final int d = KeyMaps.digVal(id);
+ final boolean binary = KeyMaps.isBinary(id);
Token lastTok = s == 0 ? null : mExpr.get(s-1);
- int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).mId : 0;
+ int lastOp = lastTok instanceof Operator ? ((Operator) lastTok).id : 0;
// Quietly replace a trailing binary operator with another one, unless the second
// operator is minus, in which case we just allow it as a unary minus.
if (binary && !KeyMaps.isPrefix(id)) {
@@ -436,7 +479,7 @@ class CalculatorExpr {
}
// s invalid and not used below.
}
- boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
+ final boolean isConstPiece = (d != KeyMaps.NOT_DIGIT || id == R.id.dec_point);
if (isConstPiece) {
// Since we treat juxtaposition as multiplication, a constant can appear anywhere.
if (s == 0) {
@@ -476,35 +519,37 @@ class CalculatorExpr {
void removeTrailingAdditiveOperators() {
while (true) {
int s = mExpr.size();
- if (s == 0) break;
+ if (s == 0) {
+ break;
+ }
Token lastTok = mExpr.get(s-1);
- if (!(lastTok instanceof Operator)) break;
- int lastOp = ((Operator) lastTok).mId;
- if (lastOp != R.id.op_add && lastOp != R.id.op_sub) break;
+ if (!(lastTok instanceof Operator)) {
+ break;
+ }
+ int lastOp = ((Operator) lastTok).id;
+ if (lastOp != R.id.op_add && lastOp != R.id.op_sub) {
+ break;
+ }
delete();
}
}
- // Append the contents of the argument expression.
- // It is assumed that the argument expression will not change,
- // and thus its pieces can be reused directly.
- // TODO: We probably only need this for expressions consisting of
- // a single PreEval "token", and may want to check that.
- void append(CalculatorExpr expr2) {
- // Check that we're not concatenating Constant or PreEval
- // tokens, since the result would look like a single constant
+ /**
+ * Append the contents of the argument expression.
+ * It is assumed that the argument expression will not change, and thus its pieces can be
+ * reused directly.
+ */
+ public void append(CalculatorExpr expr2) {
int s = mExpr.size();
int s2 = expr2.mExpr.size();
- // Check that we're not concatenating Constant or PreEval
- // tokens, since the result would look like a single constant,
- // with very mysterious results for the user.
+ // Check that we're not concatenating Constant or PreEval tokens, since the result would
+ // look like a single constant, with very mysterious results for the user.
if (s != 0 && s2 != 0) {
Token last = mExpr.get(s-1);
Token first = expr2.mExpr.get(0);
if (!(first instanceof Operator) && !(last instanceof Operator)) {
- // Fudge it by adding an explicit multiplication.
- // We would have interpreted it as such anyway, and this
- // makes it recognizable to the user.
+ // Fudge it by adding an explicit multiplication. We would have interpreted it as
+ // such anyway, and this makes it recognizable to the user.
mExpr.add(new Operator(R.id.op_mul));
}
}
@@ -513,83 +558,97 @@ class CalculatorExpr {
}
}
- // Undo the last key addition, if any.
- void delete() {
- int s = mExpr.size();
- if (s == 0) return;
+ /**
+ * Undo the last key addition, if any.
+ * Or possibly remove a trailing exponent digit.
+ */
+ public void delete() {
+ final int s = mExpr.size();
+ if (s == 0) {
+ return;
+ }
Token last = mExpr.get(s-1);
if (last instanceof Constant) {
Constant c = (Constant)last;
c.delete();
- if (!c.isEmpty()) return;
+ if (!c.isEmpty()) {
+ return;
+ }
}
mExpr.remove(s-1);
}
- void clear() {
+ /**
+ * Remove all tokens from the expression.
+ */
+ public void clear() {
mExpr.clear();
}
- boolean isEmpty() {
+ public boolean isEmpty() {
return mExpr.isEmpty();
}
- // Returns a logical deep copy of the CalculatorExpr.
- // Operator and PreEval tokens are immutable, and thus
- // aren't really copied.
+ /**
+ * Returns a logical deep copy of the CalculatorExpr.
+ * Operator and PreEval tokens are immutable, and thus aren't really copied.
+ */
public Object clone() {
- CalculatorExpr res = new CalculatorExpr();
+ CalculatorExpr result = new CalculatorExpr();
for (Token t: mExpr) {
if (t instanceof Constant) {
- res.mExpr.add((Token)(((Constant)t).clone()));
+ result.mExpr.add((Token)(((Constant)t).clone()));
} else {
- res.mExpr.add(t);
+ result.mExpr.add(t);
}
}
- return res;
+ return result;
}
// Am I just a constant?
- boolean isConstant() {
- if (mExpr.size() != 1) return false;
+ public boolean isConstant() {
+ if (mExpr.size() != 1) {
+ return false;
+ }
return mExpr.get(0) instanceof Constant;
}
- // Return a new expression consisting of a single PreEval token
- // representing the current expression.
- // The caller supplies the value, degree mode, and short
- // string representation, which must have been previously computed.
- // Thus this is guaranteed to terminate reasonably quickly.
- CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
+ /**
+ * Return a new expression consisting of a single token representing the current pre-evaluated
+ * expression.
+ * The caller supplies the value, degree mode, and short string representation, which must
+ * have been previously computed. Thus this is guaranteed to terminate reasonably quickly.
+ */
+ public CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
boolean dm, String sr) {
CalculatorExpr result = new CalculatorExpr();
- Token t = new PreEval(val, ratVal,
- new CalculatorExpr(
- (ArrayList<Token>)mExpr.clone()),
- new EvalContext(dm, mExpr.size()), sr);
+ Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
+ new EvalContext(dm, mExpr.size()), sr);
result.mExpr.add(t);
return result;
}
- // Internal evaluation functions return an EvalRet triple.
- // We compute rational (BoundedRational) results when possible, both as
- // a performance optimization, and to detect errors exactly when we can.
- private class EvalRet {
- int mPos; // Next position (expression index) to be parsed
- final CR mVal; // Constructive Real result of evaluating subexpression
- final BoundedRational mRatVal; // Exact Rational value or null if
- // irrational or hard to compute.
+ /**
+ * Internal evaluation functions return an EvalRet triple.
+ * We compute rational (BoundedRational) results when possible, both as a performance
+ * optimization, and to detect errors exactly when we can.
+ */
+ private static class EvalRet {
+ public int pos; // Next position (expression index) to be parsed.
+ public final CR val; // Constructive Real result of evaluating subexpression.
+ public final BoundedRational ratVal; // Exact Rational value or null.
EvalRet(int p, CR v, BoundedRational r) {
- mPos = p;
- mVal = v;
- mRatVal = r;
+ pos = p;
+ val = v;
+ ratVal = r;
}
}
- // And take a context argument:
+ /**
+ * Internal evaluation functions take an EvalContext argument.
+ */
private static class EvalContext {
- public final int mPrefixLength; // Length of prefix to evaluate.
- // Not explicitly saved.
+ public final int mPrefixLength; // Length of prefix to evaluate. Not explicitly saved.
public final boolean mDegreeMode;
// If we add any other kinds of evaluation modes, they go here.
EvalContext(boolean degreeMode, int len) {
@@ -625,22 +684,25 @@ class CalculatorExpr {
}
}
- // The following methods can all throw IndexOutOfBoundsException
- // in the event of a syntax error. We expect that to be caught in
- // eval below.
+ // The following methods can all throw IndexOutOfBoundsException in the event of a syntax
+ // error. We expect that to be caught in eval below.
private boolean isOperatorUnchecked(int i, int op) {
Token t = mExpr.get(i);
- if (!(t instanceof Operator)) return false;
- return ((Operator)(t)).mId == op;
+ if (!(t instanceof Operator)) {
+ return false;
+ }
+ return ((Operator)(t)).id == op;
}
private boolean isOperator(int i, int op, EvalContext ec) {
- if (i >= ec.mPrefixLength) return false;
+ if (i >= ec.mPrefixLength) {
+ return false;
+ }
return isOperatorUnchecked(i, op);
}
- static class SyntaxException extends Exception {
+ public static class SyntaxException extends Exception {
public SyntaxException() {
super();
}
@@ -649,27 +711,26 @@ class CalculatorExpr {
}
}
- // The following functions all evaluate some kind of expression
- // starting at position i in mExpr in a specified evaluation context.
- // They return both the expression value (as constructive real and,
- // if applicable, as BigInteger) and the position of the next token
+ // The following functions all evaluate some kind of expression starting at position i in
+ // mExpr in a specified evaluation context. They return both the expression value (as
+ // constructive real and, if applicable, as BoundedRational) and the position of the next token
// that was not used as part of the evaluation.
+ // This is essentially a simple recursive descent parser combined with expression evaluation.
+
private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
- Token t = mExpr.get(i);
+ final Token t = mExpr.get(i);
BoundedRational ratVal;
- CR value;
if (t instanceof Constant) {
Constant c = (Constant)t;
ratVal = c.toRational();
- value = ratVal.CRValue();
- return new EvalRet(i+1, value, ratVal);
+ return new EvalRet(i+1, ratVal.CRValue(), ratVal);
}
if (t instanceof PreEval) {
- PreEval p = (PreEval)t;
- return new EvalRet(i+1, p.mValue, p.mRatValue);
+ final PreEval p = (PreEval)t;
+ return new EvalRet(i+1, p.value, p.ratValue);
}
EvalRet argVal;
- switch(((Operator)(t)).mId) {
+ switch(((Operator)(t)).id) {
case R.id.const_pi:
return new EvalRet(i+1, CR.PI, null);
case R.id.const_e:
@@ -680,111 +741,144 @@ class CalculatorExpr {
// Does seem to accept a leading minus.
if (isOperator(i+1, R.id.op_sub, ec)) {
argVal = evalUnary(i+2, ec);
- ratVal = BoundedRational.sqrt(
- BoundedRational.negate(argVal.mRatVal));
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- argVal.mVal.negate().sqrt(), null);
+ ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ argVal.val.negate().sqrt(), null);
} else {
argVal = evalUnary(i+1, ec);
- ratVal = BoundedRational.sqrt(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.sqrt(), null);
+ ratVal = BoundedRational.sqrt(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
}
case R.id.lparen:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- return new EvalRet(argVal.mPos, argVal.mVal, argVal.mRatVal);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
case R.id.fun_sin:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.mRatVal)
- : BoundedRational.sin(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- toRadians(argVal.mVal,ec).sin(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
+ : BoundedRational.sin(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
case R.id.fun_cos:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.mRatVal)
- : BoundedRational.cos(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- toRadians(argVal.mVal,ec).cos(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
+ : BoundedRational.cos(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
case R.id.fun_tan:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.mRatVal)
- : BoundedRational.tan(argVal.mRatVal);
- if (ratVal != null) break;
- CR argCR = toRadians(argVal.mVal, ec);
- return new EvalRet(argVal.mPos,
- argCR.sin().divide(argCR.cos()), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
+ : BoundedRational.tan(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ CR argCR = toRadians(argVal.val, ec);
+ return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
case R.id.fun_ln:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.ln(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.ln(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.ln(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.ln(), null);
case R.id.fun_exp:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.exp(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos, argVal.mVal.exp(), null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.exp(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.exp(), null);
case R.id.fun_log:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = BoundedRational.log(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- argVal.mVal.ln().divide(CR.valueOf(10).ln()),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = BoundedRational.log(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
case R.id.fun_arcsin:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.mRatVal)
- : BoundedRational.asin(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .asinFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
+ : BoundedRational.asin(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
case R.id.fun_arccos:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.mRatVal)
- : BoundedRational.acos(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .acosFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
+ : BoundedRational.acos(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
case R.id.fun_arctan:
argVal = evalExpr(i+1, ec);
- if (isOperator(argVal.mPos, R.id.rparen, ec)) argVal.mPos++;
- ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.mRatVal)
- : BoundedRational.atan(argVal.mRatVal);
- if (ratVal != null) break;
- return new EvalRet(argVal.mPos,
- fromRadians(UnaryCRFunction
- .atanFunction.execute(argVal.mVal),ec),
- null);
+ if (isOperator(argVal.pos, R.id.rparen, ec)) {
+ argVal.pos++;
+ }
+ ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
+ : BoundedRational.atan(argVal.ratVal);
+ if (ratVal != null) {
+ break;
+ }
+ return new EvalRet(argVal.pos,
+ fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
default:
throw new SyntaxException("Unrecognized token in expression");
}
// We have a rational value.
- return new EvalRet(argVal.mPos, ratVal.CRValue(), ratVal);
+ return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
}
- // Compute an integral power of a constructive real.
- // Unlike the "general" case using logarithms, this handles a negative
- // base.
+ /**
+ * Compute an integral power of a constructive real.
+ * Unlike the "general" case using logarithms, this handles a negative base.
+ */
private static CR pow(CR base, BigInteger exp) {
if (exp.compareTo(BigInteger.ZERO) < 0) {
return pow(base, exp.negate()).inverse();
}
- if (exp.equals(BigInteger.ONE)) return base;
+ if (exp.equals(BigInteger.ONE)) {
+ return base;
+ }
if (exp.and(BigInteger.ONE).intValue() == 1) {
return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
}
@@ -795,24 +889,23 @@ class CalculatorExpr {
return tmp.multiply(tmp);
}
+ // Number of bits past binary point to test for integer-ness.
private static final int TEST_PREC = -100;
- // Test for integer-ness to 100 bits past binary point.
private static final BigInteger MASK =
BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
private static final CR REAL_E = CR.valueOf(1).exp();
private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
- private static final BoundedRational RATIONAL_ONE_HUNDREDTH =
- new BoundedRational(1,100);
+ private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
private static boolean isApprInt(CR x) {
BigInteger appr = x.get_appr(TEST_PREC);
return appr.and(MASK).signum() == 0;
}
private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
- EvalRet tmp = evalUnary(i, ec);
- int cpos = tmp.mPos;
- CR cval = tmp.mVal;
- BoundedRational ratVal = tmp.mRatVal;
+ final EvalRet tmp = evalUnary(i, ec);
+ int cpos = tmp.pos;
+ CR crVal = tmp.val;
+ BoundedRational ratVal = tmp.ratVal;
boolean isFact;
boolean isSquared = false;
while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
@@ -820,46 +913,45 @@ class CalculatorExpr {
isOperator(cpos, R.id.op_pct, ec)) {
if (isFact) {
if (ratVal == null) {
- // Assume it was an integer, but we
- // didn't figure it out.
+ // Assume it was an integer, but we didn't figure it out.
// KitKat may have used the Gamma function.
- if (!isApprInt(cval)) {
+ if (!isApprInt(crVal)) {
throw new ArithmeticException("factorial(non-integer)");
}
- ratVal = new BoundedRational(cval.BigIntegerValue());
+ ratVal = new BoundedRational(crVal.BigIntegerValue());
}
ratVal = BoundedRational.fact(ratVal);
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
} else if (isSquared) {
ratVal = BoundedRational.multiply(ratVal, ratVal);
if (ratVal == null) {
- cval = cval.multiply(cval);
+ crVal = crVal.multiply(crVal);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else /* percent */ {
ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
if (ratVal == null) {
- cval = cval.multiply(REAL_ONE_HUNDREDTH);
+ crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
}
++cpos;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
final EvalRet result1 = evalSuffix(i, ec);
- int cpos = result1.mPos; // current position
- CR cval = result1.mVal; // value so far
- BoundedRational ratVal = result1.mRatVal; // int value so far
+ int cpos = result1.pos; // current position
+ CR crVal = result1.val; // value so far
+ BoundedRational ratVal = result1.ratVal; // int value so far
if (isOperator(cpos, R.id.op_pow, ec)) {
- final EvalRet exp = evalSignedFactor(cpos+1, ec);
- cpos = exp.mPos;
+ final EvalRet exp = evalSignedFactor(cpos + 1, ec);
+ cpos = exp.pos;
// Try completely rational evaluation first.
- ratVal = BoundedRational.pow(ratVal, exp.mRatVal);
+ ratVal = BoundedRational.pow(ratVal, exp.ratVal);
if (ratVal != null) {
return new EvalRet(cpos, ratVal.CRValue(), ratVal);
}
@@ -867,33 +959,33 @@ class CalculatorExpr {
// Thus we handle that case separately.
// We punt if the exponent is an integer computed from irrational
// values. That wouldn't work reliably with floating point either.
- BigInteger int_exp = BoundedRational.asBigInteger(exp.mRatVal);
+ BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
if (int_exp != null) {
- cval = pow(cval, int_exp);
+ crVal = pow(crVal, int_exp);
} else {
- cval = cval.ln().multiply(exp.mVal).exp();
+ crVal = crVal.ln().multiply(exp.val).exp();
}
ratVal = null;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
final boolean negative = isOperator(i, R.id.op_sub, ec);
int cpos = negative ? i + 1 : i;
EvalRet tmp = evalFactor(cpos, ec);
- cpos = tmp.mPos;
- CR cval = negative ? tmp.mVal.negate() : tmp.mVal;
- BoundedRational ratVal = negative ? BoundedRational.negate(tmp.mRatVal)
- : tmp.mRatVal;
- return new EvalRet(cpos, cval, ratVal);
+ cpos = tmp.pos;
+ CR crVal = negative ? tmp.val.negate() : tmp.val;
+ BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
+ : tmp.ratVal;
+ return new EvalRet(cpos, crVal, ratVal);
}
private boolean canStartFactor(int i) {
if (i >= mExpr.size()) return false;
Token t = mExpr.get(i);
if (!(t instanceof Operator)) return true;
- int id = ((Operator)(t)).mId;
+ int id = ((Operator)(t)).id;
if (KeyMaps.isBinary(id)) return false;
switch (id) {
case R.id.op_fact:
@@ -908,72 +1000,136 @@ class CalculatorExpr {
EvalRet tmp = evalSignedFactor(i, ec);
boolean is_mul = false;
boolean is_div = false;
- int cpos = tmp.mPos; // Current position in expression.
- CR cval = tmp.mVal; // Current value.
- BoundedRational ratVal = tmp.mRatVal; // Current rational value.
+ int cpos = tmp.pos; // Current position in expression.
+ CR crVal = tmp.val; // Current value.
+ BoundedRational ratVal = tmp.ratVal; // Current rational value.
while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
|| (is_div = isOperator(cpos, R.id.op_div, ec))
|| canStartFactor(cpos)) {
if (is_mul || is_div) ++cpos;
tmp = evalSignedFactor(cpos, ec);
if (is_div) {
- ratVal = BoundedRational.divide(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.divide(tmp.mVal);
+ crVal = crVal.divide(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else {
- ratVal = BoundedRational.multiply(ratVal, tmp.mRatVal);
+ ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.multiply(tmp.mVal);
+ crVal = crVal.multiply(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
}
- cpos = tmp.mPos;
+ cpos = tmp.pos;
is_mul = is_div = false;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
+ }
+
+ /**
+ * Is the subexpression starting at pos a simple percent constant?
+ * This is used to recognize exppressions like 200+10%, which we handle specially.
+ * This is defined as a Constant or PreEval token, followed by a percent sign, and followed
+ * by either nothing or an additive operator.
+ * Note that we are intentionally far more restrictive in recognizing such expressions than
+ * e.g. http://blogs.msdn.com/b/oldnewthing/archive/2008/01/10/7047497.aspx .
+ * When in doubt, we fall back to the the naive interpretation of % as 1/100.
+ * Note that 100+(10)% yields 100.1 while 100+10% yields 110. This may be controversial,
+ * but is consistent with Google web search.
+ */
+ private boolean isPercent(int pos) {
+ if (mExpr.size() < pos + 2 || !isOperatorUnchecked(pos + 1, R.id.op_pct)) {
+ return false;
+ }
+ Token number = mExpr.get(pos);
+ if (number instanceof Operator) {
+ return false;
+ }
+ if (mExpr.size() == pos + 2) {
+ return true;
+ }
+ if (!(mExpr.get(pos + 2) instanceof Operator)) {
+ return false;
+ }
+ Operator op = (Operator) mExpr.get(pos + 2);
+ return op.id == R.id.op_add || op.id == R.id.op_sub;
+ }
+
+ /**
+ * Compute the multiplicative factor corresponding to an N% addition or subtraction.
+ * @param pos position of Constant or PreEval expression token corresponding to N
+ * @param isSubtraction this is a subtraction, as opposed to addition
+ * @param ec usable evaluation contex; only length matters
+ * @return Rational and CR values; position is pos + 2, i.e. after percent sign
+ */
+ private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
+ throws SyntaxException {
+ EvalRet tmp = evalUnary(pos, ec);
+ BoundedRational ratVal = isSubtraction ? BoundedRational.negate(tmp.ratVal)
+ : tmp.ratVal;
+ CR crVal = isSubtraction ? tmp.val.negate() : tmp.val;
+ ratVal = BoundedRational.add(BoundedRational.ONE,
+ BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH));
+ if (ratVal == null) {
+ crVal = CR.ONE.add(crVal.multiply(REAL_ONE_HUNDREDTH));
+ } else {
+ crVal = ratVal.CRValue();
+ }
+ return new EvalRet(pos + 2 /* after percent sign */, crVal, ratVal);
}
private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
EvalRet tmp = evalTerm(i, ec);
boolean is_plus;
- int cpos = tmp.mPos;
- CR cval = tmp.mVal;
- BoundedRational ratVal = tmp.mRatVal;
+ int cpos = tmp.pos;
+ CR crVal = tmp.val;
+ BoundedRational ratVal = tmp.ratVal;
while ((is_plus = isOperator(cpos, R.id.op_add, ec))
|| isOperator(cpos, R.id.op_sub, ec)) {
- tmp = evalTerm(cpos+1, ec);
- if (is_plus) {
- ratVal = BoundedRational.add(ratVal, tmp.mRatVal);
+ if (isPercent(cpos + 1)) {
+ tmp = getPercentFactor(cpos + 1, !is_plus, ec);
+ ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
if (ratVal == null) {
- cval = cval.add(tmp.mVal);
+ crVal = crVal.multiply(tmp.val);
} else {
- cval = ratVal.CRValue();
+ crVal = ratVal.CRValue();
}
} else {
- ratVal = BoundedRational.subtract(ratVal, tmp.mRatVal);
- if (ratVal == null) {
- cval = cval.subtract(tmp.mVal);
+ tmp = evalTerm(cpos + 1, ec);
+ if (is_plus) {
+ ratVal = BoundedRational.add(ratVal, tmp.ratVal);
+ if (ratVal == null) {
+ crVal = crVal.add(tmp.val);
+ } else {
+ crVal = ratVal.CRValue();
+ }
} else {
- cval = ratVal.CRValue();
+ ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
+ if (ratVal == null) {
+ crVal = crVal.subtract(tmp.val);
+ } else {
+ crVal = ratVal.CRValue();
+ }
}
}
- cpos = tmp.mPos;
+ cpos = tmp.pos;
}
- return new EvalRet(cpos, cval, ratVal);
+ return new EvalRet(cpos, crVal, ratVal);
}
- // Externally visible evaluation result.
- public class EvalResult {
- EvalResult (CR val, BoundedRational ratVal) {
- mVal = val;
- mRatVal = ratVal;
+ /**
+ * Externally visible evaluation result.
+ */
+ public static class EvalResult {
+ public final CR val;
+ public final BoundedRational ratVal;
+ EvalResult (CR v, BoundedRational rv) {
+ val = v;
+ ratVal = rv;
}
- final CR mVal;
- final BoundedRational mRatVal;
}
/**
@@ -985,13 +1141,15 @@ class CalculatorExpr {
Token last = mExpr.get(result - 1);
if (!(last instanceof Operator)) break;
Operator o = (Operator)last;
- if (!KeyMaps.isBinary(o.mId)) break;
+ if (!KeyMaps.isBinary(o.id)) break;
--result;
}
return result;
}
- // Is the current expression worth evaluating?
+ /**
+ * Is the current expression worth evaluating?
+ */
public boolean hasInterestingOps() {
int last = trailingBinaryOpsStart();
int first = 0;
@@ -1011,9 +1169,9 @@ class CalculatorExpr {
/**
* Evaluate the expression excluding trailing binary operators.
- * Errors result in exceptions, most of which are unchecked.
- * Should not be called concurrently with modification of the expression.
- * May take a very long time; avoid calling from UI thread.
+ * Errors result in exceptions, most of which are unchecked. Should not be called
+ * concurrently with modification of the expression. May take a very long time; avoid calling
+ * from UI thread.
*
* @param degreeMode use degrees rather than radians
*/
@@ -1022,18 +1180,17 @@ class CalculatorExpr {
// and BoundedRational.
{
try {
- // We currently never include trailing binary operators, but include
- // other trailing operators.
- // Thus we usually, but not always, display results for prefixes
- // of valid expressions, and don't generate an error where we previously
- // displayed an instant result. This reflects the Android L design.
+ // We currently never include trailing binary operators, but include other trailing
+ // operators. Thus we usually, but not always, display results for prefixes of valid
+ // expressions, and don't generate an error where we previously displayed an instant
+ // result. This reflects the Android L design.
int prefixLen = trailingBinaryOpsStart();
EvalContext ec = new EvalContext(degreeMode, prefixLen);
EvalRet res = evalExpr(0, ec);
- if (res.mPos != prefixLen) {
+ if (res.pos != prefixLen) {
throw new SyntaxException("Failed to parse full expression");
}
- return new EvalResult(res.mVal, res.mRatVal);
+ return new EvalResult(res.val, res.ratVal);
} catch (IndexOutOfBoundsException e) {
throw new SyntaxException("Unexpected expression end");
}
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 1efa67b..5c96867 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -335,9 +335,9 @@ public class CalculatorResult extends AlignedTextView {
/*
* Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
- * Unlike Evaluator.getMsdPos, we treat a final 1 as significant.
+ * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
*/
- public static int getNaiveMsdIndex(String s) {
+ public static int getNaiveMsdIndexOf(String s) {
int len = s.length();
for (int i = 0; i < len; ++i) {
char c = s.charAt(i);
@@ -349,14 +349,14 @@ public class CalculatorResult extends AlignedTextView {
}
// Format a result returned by Evaluator.getString() into a single line containing ellipses
- // (if appropriate) and an exponent (if appropriate). prec is the value that was passed to
- // getString and thus identifies the significance of the rightmost digit.
+ // (if appropriate) and an exponent (if appropriate). precOffset is the value that was passed
+ // to getString and thus identifies the significance of the rightmost digit.
// A value of 1 means the rightmost digits corresponds to tenths.
// maxDigs is the maximum number of characters in the result.
// We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in
// the display.
// If forcePrecision is true, we make sure that the last displayed digit corresponds to
- // prec, and allow maxDigs to be exceeded in assing the exponent.
+ // precOffset, and allow maxDigs to be exceeded in assing the exponent.
// We add two distinct kinds of exponents:
// (1) If the final result contains the leading digit we use standard scientific notation.
// (2) If not, we add an exponent corresponding to an interpretation of the final result as
@@ -369,9 +369,13 @@ public class CalculatorResult extends AlignedTextView {
public String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
final int minusSpace = negative ? 1 : 0;
- final int msdIndex = truncated ? -1 : getNaiveMsdIndex(in); // INVALID_MSD is OK.
- final int decIndex = in.indexOf('.');
+ final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in); // INVALID_MSD is OK.
String result = in;
+ if (truncated || (negative && result.charAt(0) != '-')) {
+ result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
+ // Ellipsis may be removed again in the type(1) scientific notation case.
+ }
+ final int decIndex = result.indexOf('.');
lastDisplayedOffset[0] = precOffset;
if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
&& msdIndex - decIndex > MAX_LEADING_ZEROES + 1) && precOffset != -1) {
@@ -400,42 +404,38 @@ public class CalculatorResult extends AlignedTextView {
exponent = initExponent + resLen - msdIndex - 1;
hasPoint = true;
}
- if (exponent != 0 || truncated) {
- // Actually add the exponent of either type:
- if (!forcePrecision) {
- int dropDigits; // Digits to drop to make room for exponent.
- if (hasPoint) {
- // Type (1) exponent.
- // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
- dropDigits = expLen(exponent);
- if (dropDigits >= result.length() - 1) {
- // Jumpy is better than no mantissa. Probably impossible anyway.
- dropDigits = Math.max(result.length() - 2, 0);
- }
- } else {
- // Type (2) exponent.
- // Exponent depends on the number of digits we drop, which depends on
- // exponent ...
- for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
- ++dropDigits) {}
- exponent = initExponent + dropDigits;
- if (precOffset - dropDigits > mLsdOffset) {
- // This can happen if e.g. result = 10^40 + 10^10
- // It turns out we would otherwise display ...10e9 because it takes
- // the same amount of space as ...1e10 but shows one more digit.
- // But we don't want to display a trailing zero, even if it's free.
- ++dropDigits;
- ++exponent;
- }
+ // Exponent can't be zero.
+ // Actually add the exponent of either type:
+ if (!forcePrecision) {
+ int dropDigits; // Digits to drop to make room for exponent.
+ if (hasPoint) {
+ // Type (1) exponent.
+ // Drop digits even if there is room. Otherwise the scrolling gets jumpy.
+ dropDigits = expLen(exponent);
+ if (dropDigits >= result.length() - 1) {
+ // Jumpy is better than no mantissa. Probably impossible anyway.
+ dropDigits = Math.max(result.length() - 2, 0);
+ }
+ } else {
+ // Type (2) exponent.
+ // Exponent depends on the number of digits we drop, which depends on
+ // exponent ...
+ for (dropDigits = 2; expLen(initExponent + dropDigits) > dropDigits;
+ ++dropDigits) {}
+ exponent = initExponent + dropDigits;
+ if (precOffset - dropDigits > mLsdOffset) {
+ // This can happen if e.g. result = 10^40 + 10^10
+ // It turns out we would otherwise display ...10e9 because it takes
+ // the same amount of space as ...1e10 but shows one more digit.
+ // But we don't want to display a trailing zero, even if it's free.
+ ++dropDigits;
+ ++exponent;
}
- result = result.substring(0, result.length() - dropDigits);
- lastDisplayedOffset[0] -= dropDigits;
}
- result = result + "E" + Integer.toString(exponent);
- } // else don't add zero exponent
- }
- if (truncated || negative && result.charAt(0) != '-') {
- result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
+ result = result.substring(0, result.length() - dropDigits);
+ lastDisplayedOffset[0] -= dropDigits;
+ }
+ result = result + "E" + Integer.toString(exponent);
}
return result;
}
diff --git a/src/com/android/calculator2/CalculatorText.java b/src/com/android/calculator2/CalculatorText.java
index 109c2af..f944071 100644
--- a/src/com/android/calculator2/CalculatorText.java
+++ b/src/com/android/calculator2/CalculatorText.java
@@ -138,18 +138,22 @@ public class CalculatorText extends AlignedTextView implements View.OnLongClickL
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Prevent shrinking/resizing with our variable textSize.
- if (!isLaidOut()) {
- setTextSize(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize);
- setMinHeight(getLineHeight() + getCompoundPaddingBottom() + getCompoundPaddingTop());
- }
-
// Re-calculate our textSize based on new width.
final int width = MeasureSpec.getSize(widthMeasureSpec)
- getPaddingLeft() - getPaddingRight();
if (mWidthConstraint != width) {
mWidthConstraint = width;
- setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText()));
+
+ if (!isLaidOut()) {
+ // Prevent shrinking/resizing with our variable textSize.
+ setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize,
+ false /* notifyListener */);
+ setMinHeight(getLineHeight() + getCompoundPaddingBottom()
+ + getCompoundPaddingTop());
+ }
+
+ setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText()),
+ false);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -164,16 +168,19 @@ public class CalculatorText extends AlignedTextView implements View.OnLongClickL
setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
}
- @Override
- public void setTextSize(int unit, float size) {
+ private void setTextSizeInternal(int unit, float size, boolean notifyListener) {
final float oldTextSize = getTextSize();
super.setTextSize(unit, size);
-
- if (mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
+ if (notifyListener && mOnTextSizeChangeListener != null && getTextSize() != oldTextSize) {
mOnTextSizeChangeListener.onTextSizeChanged(this, oldTextSize);
}
}
+ @Override
+ public void setTextSize(int unit, float size) {
+ setTextSizeInternal(unit, size, true);
+ }
+
public float getMinimumTextSize() {
return mMinimumTextSize;
}
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index b779a68..936d618 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -14,68 +14,6 @@
* limitations under the License.
*/
-//
-// This implements the calculator evaluation logic.
-// An evaluation is started with a call to evaluateAndShowResult().
-// This starts an asynchronous computation, which requests display
-// of the initial result, when available. When initial evaluation is
-// complete, it calls the calculator onEvaluate() method.
-// This occurs in a separate event, and may happen quite a bit
-// later. Once a result has been computed, and before the underlying
-// expression is modified, the getString method may be used to produce
-// Strings that represent approximations to various precisions.
-//
-// Actual expressions being evaluated are represented as CalculatorExprs,
-// which are just slightly preprocessed sequences of keypresses.
-//
-// The Evaluator owns the expression being edited and associated
-// state needed for evaluating it. It provides functionality for
-// saving and restoring this state. However the current
-// CalculatorExpr is exposed to the client, and may be directly modified
-// after cancelling any in-progress computations by invoking the
-// cancelAll() method.
-//
-// When evaluation is requested by the user, we invoke the eval
-// method on the CalculatorExpr from a background AsyncTask.
-// A subsequent getString() callback returns immediately, though it may
-// return a result containing placeholder '?' characters.
-// In that case we start a background task, which invokes the
-// onReevaluate() callback when it completes.
-// In both cases, the background task
-// computes the appropriate result digits by evaluating
-// the constructive real (CR) returned by CalculatorExpr.eval()
-// to the required precision.
-//
-// We cache the best approximation we have already computed.
-// We compute generously to allow for
-// some scrolling without recomputation and to minimize the chance of
-// digits flipping from "0000" to "9999". The best known
-// result approximation is maintained as a string by mCache (and
-// in a different format by the CR representation of the result).
-// When we are in danger of not having digits to display in response
-// to further scrolling, we initiate a background computation to higher
-// precision. If we actually do fall behind, we display placeholder
-// characters, e.g. blanks, and schedule a display update when the computation
-// completes.
-// The code is designed to ensure that the error in the displayed
-// result (excluding any placeholder characters) is always strictly less than 1 in
-// the last displayed digit. Typically we actually display a prefix
-// of a result that has this property and additionally is computed to
-// a significantly higher precision. Thus we almost always round correctly
-// towards zero. (Fully correct rounding towards zero is not computable.)
-//
-// Initial expression evaluation may time out. This may happen in the
-// case of domain errors such as division by zero, or for large computations.
-// We do not currently time out reevaluations to higher precision, since
-// the original evaluation prevcluded a domain error that could result
-// in non-termination. (We may discover that a presumed zero result is
-// actually slightly negative when re-evaluated; but that results in an
-// exception, which we can handle.) The user can abort either kind
-// of computation.
-//
-// We ensure that only one evaluation of either kind (AsyncReevaluator
-// or AsyncDisplayResult) is running at a time.
-
package com.android.calculator2;
import android.app.AlertDialog;
@@ -86,6 +24,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.preference.PreferenceManager;
+import android.support.annotation.VisibleForTesting;
import android.util.Log;
import com.hp.creals.CR;
@@ -100,102 +39,145 @@ import java.util.Date;
import java.util.Random;
import java.util.TimeZone;
+/**
+ * This implements the calculator evaluation logic. The underlying expression is constructed and
+ * edited with append(), delete(), and clear(). An evaluation an then be started with a call to
+ * evaluateAndShowResult() or requireResult(). This starts an asynchronous computation, which
+ * requests display of the initial result, when available. When initial evaluation is complete,
+ * it calls the calculator onEvaluate() method. This occurs in a separate event, possibly quite a
+ * bit later. Once a result has been computed, and before the underlying expression is modified,
+ * the getString() method may be used to produce Strings that represent approximations to various
+ * precisions.
+ *
+ * Actual expressions being evaluated are represented as {@link CalculatorExpr}s.
+ *
+ * The Evaluator owns the expression being edited and all associated state needed for evaluating
+ * it. It provides functionality for saving and restoring this state. However the current
+ * CalculatorExpr is exposed to the client, and may be directly accessed after cancelling any
+ * in-progress computations by invoking the cancelAll() method.
+ *
+ * When evaluation is requested, we invoke the eval() method on the CalculatorExpr from a
+ * background AsyncTask. A subsequent getString() callback returns immediately, though it may
+ * return a result containing placeholder ' ' characters. If we had to return palceholder
+ * characters, we start a background task, which invokes the onReevaluate() callback when it
+ * completes. In either case, the background task computes the appropriate result digits by
+ * evaluating the constructive real (CR) returned by CalculatorExpr.eval() to the required
+ * precision.
+ *
+ * We cache the best decimal approximation we have already computed. We compute generously to
+ * allow for some scrolling without recomputation and to minimize the chance of digits flipping
+ * from "0000" to "9999". The best known result approximation is maintained as a string by
+ * mResultString (and in a different format by the CR representation of the result). When we are
+ * in danger of not having digits to display in response to further scrolling, we also initiate a
+ * background computation to higher precision, as if we had generated placeholder characters.
+ *
+ * The code is designed to ensure that the error in the displayed result (excluding any
+ * placeholder characters) is always strictly less than 1 in the last displayed digit. Typically
+ * we actually display a prefix of a result that has this property and additionally is computed to
+ * a significantly higher precision. Thus we almost always round correctly towards zero. (Fully
+ * correct rounding towards zero is not computable, at least given our representation.)
+ *
+ * Initial expression evaluation may time out. This may happen in the case of domain errors such
+ * as division by zero, or for large computations. We do not currently time out reevaluations to
+ * higher precision, since the original evaluation precluded a domain error that could result in
+ * non-termination. (We may discover that a presumed zero result is actually slightly negative
+ * when re-evaluated; but that results in an exception, which we can handle.) The user can abort
+ * either kind of computation.
+ *
+ * We ensure that only one evaluation of either kind (AsyncEvaluator or AsyncReevaluator) is
+ * running at a time.
+ */
class Evaluator {
- private static final String KEY_PREF_DEGREE_MODE = "degree_mode";
+ // When naming variables and fields, "Offset" denotes a character offset in a string
+ // representing a decimal number, where the offset is relative to the decimal point. 1 =
+ // tenths position, -1 = units position. Integer.MAX_VALUE is sometimes used for the offset
+ // of the last digit in an a nonterminating decimal expansion. We use the suffix "Index" to
+ // denote a zero-based absolute index into such a string.
- private final Calculator mCalculator;
- private final CalculatorResult mResult; // The result display View
- private CalculatorExpr mExpr; // Current calculator expression
- private CalculatorExpr mSaved; // Last saved expression.
- // Either null or contains a single
- // preevaluated node.
- private String mSavedName; // A hopefully unique name associated
- // with mSaved.
- // The following are valid only if an evaluation
- // completed successfully.
- private CR mVal; // value of mExpr as constructive real
- private BoundedRational mRatVal; // value of mExpr as rational or null
- private int mLastDigs; // Last digit argument passed to getString()
- // for this result, or the initial preferred
- // precision.
- private boolean mDegreeMode; // Currently in degree (not radian) mode
- private final Handler mTimeoutHandler;
-
- static final BigInteger BIG_MILLION = BigInteger.valueOf(1000000);
+ private static final String KEY_PREF_DEGREE_MODE = "degree_mode";
+ // The minimum number of extra digits we always try to compute to improve the chance of
+ // producing a correctly-rounded-towards-zero result. The extra digits can be displayed to
+ // avoid generating placeholder digits, but should only be displayed briefly while computing.
private static final int EXTRA_DIGITS = 20;
- // Extra computed digits to minimize probably we will have
- // to change our minds about digits we already displayed.
- // (The correct digits are technically not computable using our
- // representation: An off by one error in the last digits
- // can affect earlier ones, even though the display is
- // always within one in the lsd. This is only visible
- // for results that end in EXTRA_DIGITS 9s or 0s, but are
- // not integers.)
- // We do use these extra digits to display while we are
- // computing the correct answer. Thus they may be
- // temporarily visible.
- private static final int EXTRA_DIVISOR = 5;
- // We add the length of the previous result divided by
- // EXTRA_DIVISOR to try to recover recompute latency when
- // scrolling through a long result.
- private static final int PRECOMPUTE_DIGITS = 30;
- private static final int PRECOMPUTE_DIVISOR = 5;
- // When we have to reevaluate, we compute an extra
- // PRECOMPUTE_DIGITS
- // + <current_result_length>/PRECOMPUTE_DIVISOR digits.
- // The last term is dropped if prec < 0.
-
- // We cache the result as a string to accelerate scrolling.
- // The cache is filled in by the UI thread, but this may
- // happen asynchronously, much later than the request.
- private String mCache; // Current best known result, which includes
- private int mCacheDigs = 0; // mCacheDigs digits to the right of the
- // decimal point. Always positive.
- // mCache is valid when non-null
- // unless the expression has been
- // changed since the last evaluation call.
- private int mCacheDigsReq; // Number of digits that have been
- // requested. Only touched by UI
- // thread.
- public static final int INVALID_MSD = Integer.MAX_VALUE;
- private int mMsd = INVALID_MSD; // Position of most significant digit
- // in current cached result, if determined.
- // This is just the index in mCache
- // holding the msd.
+
+ // We adjust EXTRA_DIGITS by adding the length of the previous result divided by
+ // EXTRA_DIVISOR. This helps hide recompute latency when long results are requested;
+ // We start the recomputation substantially before the need is likely to be visible.
+ private static final int EXTRA_DIVISOR = 5;
+
+ // In addition to insisting on extra digits (see above), we minimize reevaluation
+ // frequency by precomputing an extra PRECOMPUTE_DIGITS
+ // + <current_precision_offset>/PRECOMPUTE_DIVISOR digits, whenever we are forced to
+ // reevaluate. The last term is dropped if prec < 0.
+ private static final int PRECOMPUTE_DIGITS = 30;
+ private static final int PRECOMPUTE_DIVISOR = 5;
+
+ // Initial evaluation precision. Enough to guarantee that we can compute the short
+ // representation, and that we rarely have to evaluate nonzero results to MAX_MSD_PREC_OFFSET.
+ // It also helps if this is at least EXTRA_DIGITS + display width, so that we don't
+ // immediately need a second evaluation.
private static final int INIT_PREC = 50;
- // Initial evaluation precision. Enough to guarantee
- // that we can compute the short representation, and that
- // we rarely have to evaluate nonzero results to
- // MAX_MSD_PREC. It also helps if this is at least
- // EXTRA_DIGITS + display width, so that we don't
- // immediately need a second evaluation.
- private static final int MAX_MSD_PREC = 320;
- // The largest number of digits to the right
- // of the decimal point to which we will
- // evaluate to compute proper scientific
- // notation for values close to zero.
- // Chosen to ensure that we always to better than
- // IEEE double precision at identifying nonzeros.
+
+ // The largest number of digits to the right of the decimal point to which we will evaluate to
+ // compute proper scientific notation for values close to zero. Chosen to ensure that we
+ // always to better than IEEE double precision at identifying nonzeros.
+ private static final int MAX_MSD_PREC_OFFSET = 320;
+
+ // If we can replace an exponent by this many leading zeroes, we do so. Also used in
+ // estimating exponent size for truncating short representation.
private static final int EXP_COST = 3;
- // If we can replace an exponent by this many leading zeroes,
- // we do so. Also used in estimating exponent size for
- // truncating short representation.
- private AsyncReevaluator mCurrentReevaluator;
- // The one and only un-cancelled and currently running reevaluator.
- // Touched only by UI thread.
+ private final Calculator mCalculator;
+ private final CalculatorResult mResult;
+
+ // The current caluclator expression.
+ private CalculatorExpr mExpr;
- private AsyncDisplayResult mEvaluator;
- // Currently running expression evaluator, if any.
+ // Last saved expression. Either null or contains a single CalculatorExpr.PreEval node.
+ private CalculatorExpr mSaved;
+ // A hopefully unique name associated with mSaved.
+ private String mSavedName;
+
+ // The expression may have changed since the last evaluation in ways that would affect its
+ // value.
private boolean mChangedValue;
- // The expression may have changed since the last evaluation in ways that would
- // affect its value.
private SharedPreferences mSharedPrefs;
+ private boolean mDegreeMode; // Currently in degree (not radian) mode.
+
+ private final Handler mTimeoutHandler; // Used to schedule evaluation timeouts.
+
+ // The following are valid only if an evaluation completed successfully.
+ private CR mVal; // Value of mExpr as constructive real.
+ private BoundedRational mRatVal; // Value of mExpr as rational or null.
+
+ // We cache the best known decimal result in mResultString. Whenever that is
+ // non-null, it is computed to exactly mResultStringOffset, which is always > 0.
+ // The cache is filled in by the UI thread.
+ // Valid only if mResultString is non-null and !mChangedValue.
+ private String mResultString;
+ private int mResultStringOffset = 0;
+
+ // Number of digits to which (possibly incomplete) evaluation has been requested.
+ // Only accessed by UI thread.
+ private int mResultStringOffsetReq; // Number of digits that have been
+
+ public static final int INVALID_MSD = Integer.MAX_VALUE;
+
+ // Position of most significant digit in current cached result, if determined. This is just
+ // the index in mResultString holding the msd.
+ private int mMsdIndex = INVALID_MSD;
+
+ // Currently running expression evaluator, if any.
+ private AsyncEvaluator mEvaluator;
+
+ // The one and only un-cancelled and currently running reevaluator. Touched only by UI thread.
+ private AsyncReevaluator mCurrentReevaluator;
+
Evaluator(Calculator calculator,
CalculatorResult resultDisplay) {
mCalculator = calculator;
@@ -209,86 +191,36 @@ class Evaluator {
mDegreeMode = mSharedPrefs.getBoolean(KEY_PREF_DEGREE_MODE, false);
}
- // Result of asynchronous reevaluation
- class ReevalResult {
- ReevalResult(String s, int p) {
- mNewCache = s;
- mNewCacheDigs = p;
- }
- final String mNewCache;
- final int mNewCacheDigs;
- }
-
- // Compute new cache contents accurate to prec digits to the right
- // of the decimal point. Ensure that redisplay() is called after
- // doing so. If the evaluation fails for reasons other than a
- // timeout, ensure that DisplayError() is called.
- class AsyncReevaluator extends AsyncTask<Integer, Void, ReevalResult> {
- @Override
- protected ReevalResult doInBackground(Integer... prec) {
- try {
- int eval_prec = prec[0].intValue();
- return new ReevalResult(mVal.toString(eval_prec), eval_prec);
- } catch(ArithmeticException e) {
- return null;
- } catch(CR.PrecisionOverflowException e) {
- return null;
- } catch(CR.AbortedException e) {
- // Should only happen if the task was cancelled,
- // in which case we don't look at the result.
- return null;
- }
- }
- @Override
- protected void onPostExecute(ReevalResult result) {
- if (result == null) {
- // This should only be possible in the extremely rare
- // case of encountering a domain error while reevaluating
- // or in case of a precision overflow. We don't know of
- // a way to get the latter with a plausible amount of
- // user input.
- mCalculator.onError(R.string.error_nan);
- } else {
- if (result.mNewCacheDigs < mCacheDigs) {
- throw new AssertionError("Unexpected onPostExecute timing");
- }
- mCache = result.mNewCache;
- mCacheDigs = result.mNewCacheDigs;
- mCalculator.onReevaluate();
- }
- mCurrentReevaluator = null;
- }
- // On cancellation we do nothing; invoker should have
- // left no trace of us.
- }
-
- // Result of initial asynchronous computation
+ /**
+ * Result of initial asynchronous result computation.
+ * Represents either an error or a result computed to an initial evaluation precision.
+ */
private static class InitialResult {
- InitialResult(CR val, BoundedRational ratVal, String s, int p, int idp) {
- mErrorResourceId = Calculator.INVALID_RES_ID;
- mVal = val;
- mRatVal = ratVal;
- mNewCache = s;
- mNewCacheDigs = p;
- mInitDisplayPrec = idp;
+ public final int errorResourceId; // Error string or INVALID_RES_ID.
+ public final CR val; // Constructive real value.
+ public final BoundedRational ratVal; // Rational value or null.
+ public final String newResultString; // Null iff it can't be computed.
+ public final int newResultStringOffset;
+ public final int initDisplayOffset;
+ InitialResult(CR v, BoundedRational rv, String s, int p, int idp) {
+ errorResourceId = Calculator.INVALID_RES_ID;
+ val = v;
+ ratVal = rv;
+ newResultString = s;
+ newResultStringOffset = p;
+ initDisplayOffset = idp;
}
- InitialResult(int errorResourceId) {
- mErrorResourceId = errorResourceId;
- mVal = CR.valueOf(0);
- mRatVal = BoundedRational.ZERO;
- mNewCache = "BAD";
- mNewCacheDigs = 0;
- mInitDisplayPrec = 0;
+ InitialResult(int errorId) {
+ errorResourceId = errorId;
+ val = CR.valueOf(0);
+ ratVal = BoundedRational.ZERO;
+ newResultString = "BAD";
+ newResultStringOffset = 0;
+ initDisplayOffset = 0;
}
boolean isError() {
- return mErrorResourceId != Calculator.INVALID_RES_ID;
+ return errorResourceId != Calculator.INVALID_RES_ID;
}
- final int mErrorResourceId;
- final CR mVal;
- final BoundedRational mRatVal;
- final String mNewCache; // Null iff it can't be computed.
- final int mNewCacheDigs;
- final int mInitDisplayPrec;
}
private void displayCancelledMessage() {
@@ -302,52 +234,71 @@ class Evaluator {
.show();
}
- private final long MAX_TIMEOUT = 15000;
- // Milliseconds.
- // Longer is unlikely to help unless
- // we get more heap space.
- private long mTimeout = 2000; // Timeout for requested evaluations,
- // in milliseconds.
- // This is currently not saved and restored
- // with the state; we reset
- // the timeout when the
- // calculator is restarted.
- // We'll call that a feature; others
- // might argue it's a bug.
+ // Timeout handling.
+ // Expressions are evaluated with a sort timeout or a long timeout.
+ // Each implies different maxima on both computation time and bit length.
+ // We recheck bit length separetly to avoid wasting time on decimal conversions that are
+ // destined to fail.
+
+ /**
+ * Is a long timeout in effect for the main expression?
+ */
+ private boolean mLongTimeout = false;
+
+ /**
+ * Is a long timeout in effect for the saved expression?
+ */
+ private boolean mLongSavedTimeout = false;
+
+ /**
+ * Return the timeout in milliseconds.
+ * @param longTimeout a long timeout is in effect
+ */
+ private long getTimeout(boolean longTimeout) {
+ return longTimeout ? 15000 : 2000;
+ // Exceeding a few tens of seconds increases the risk of running out of memory
+ // and impacting the rest of the system.
+ }
+
+ /**
+ * Return the maximum number of bits in the result. Longer results are assumed to time out.
+ * @param longTimeout a long timeout is in effect
+ */
+ private int getMaxResultBits(boolean longTimeout) {
+ return longTimeout ? 350000 : 120000;
+ }
+
+ /**
+ * Timeout for unrequested, speculative evaluations, in milliseconds.
+ */
private final long QUICK_TIMEOUT = 1000;
- // Timeout for unrequested, speculative
- // evaluations, in milliseconds.
- private int mMaxResultBits = 120000; // Don't try to display a larger result.
- private final int MAX_MAX_RESULT_BITS = 350000; // Long timeout version.
- private final int QUICK_MAX_RESULT_BITS = 50000; // Instant result version.
+
+ /**
+ * Maximum result bit length for unrequested, speculative evaluations.
+ */
+ private final int QUICK_MAX_RESULT_BITS = 50000;
private void displayTimeoutMessage() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(mCalculator)
- .setMessage(R.string.timeout)
- .setNegativeButton(R.string.dismiss, null /* listener */);
- if (mTimeout != MAX_TIMEOUT) {
- builder.setPositiveButton(R.string.ok_remove_timeout,
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface d, int which) {
- mTimeout = MAX_TIMEOUT;
- mMaxResultBits = MAX_MAX_RESULT_BITS;
- }
- });
- }
- builder.show();
+ AlertDialogFragment.showMessageDialog(mCalculator, mCalculator.getString(R.string.timeout),
+ (mLongTimeout ? null : mCalculator.getString(R.string.ok_remove_timeout)));
+ }
+
+ public void setLongTimeOut() {
+ mLongTimeout = true;
}
- // Compute initial cache contents and result when we're good and ready.
- // We leave the expression display up, with scrolling
- // disabled, until this computation completes.
- // Can result in an error display if something goes wrong.
- // By default we set a timeout to catch runaway computations.
- class AsyncDisplayResult extends AsyncTask<Void, Void, InitialResult> {
+ /**
+ * Compute initial cache contents and result when we're good and ready.
+ * We leave the expression display up, with scrolling disabled, until this computation
+ * completes. Can result in an error display if something goes wrong. By default we set a
+ * timeout to catch runaway computations.
+ */
+ class AsyncEvaluator extends AsyncTask<Void, Void, InitialResult> {
private boolean mDm; // degrees
private boolean mRequired; // Result was requested by user.
private boolean mQuiet; // Suppress cancellation message.
private Runnable mTimeoutRunnable = null;
- AsyncDisplayResult(boolean dm, boolean required) {
+ AsyncEvaluator(boolean dm, boolean required) {
mDm = dm;
mRequired = required;
mQuiet = !required;
@@ -370,7 +321,7 @@ class Evaluator {
}
@Override
protected void onPreExecute() {
- long timeout = mRequired ? mTimeout : QUICK_TIMEOUT;
+ long timeout = mRequired ? getTimeout(mLongTimeout) : QUICK_TIMEOUT;
mTimeoutRunnable = new Runnable() {
@Override
public void run() {
@@ -379,12 +330,15 @@ class Evaluator {
};
mTimeoutHandler.postDelayed(mTimeoutRunnable, timeout);
}
+ /**
+ * Is a computed result too big for decimal conversion?
+ */
private boolean isTooBig(CalculatorExpr.EvalResult res) {
- int maxBits = mRequired ? mMaxResultBits : QUICK_MAX_RESULT_BITS;
- if (res.mRatVal != null) {
- return res.mRatVal.wholeNumberBits() > maxBits;
+ int maxBits = mRequired ? getMaxResultBits(mLongTimeout) : QUICK_MAX_RESULT_BITS;
+ if (res.ratVal != null) {
+ return res.ratVal.wholeNumberBits() > maxBits;
} else {
- return res.mVal.get_appr(maxBits).bitLength() > 2;
+ return res.val.get_appr(maxBits).bitLength() > 2;
}
}
@Override
@@ -395,35 +349,33 @@ class Evaluator {
// Avoid starting a long uninterruptible decimal conversion.
return new InitialResult(R.string.timeout);
}
- int prec = INIT_PREC;
- String initCache = res.mVal.toString(prec);
- int msd = getMsdPos(initCache);
- if (BoundedRational.asBigInteger(res.mRatVal) == null
+ int precOffset = INIT_PREC;
+ String initResult = res.val.toString(precOffset);
+ int msd = getMsdIndexOf(initResult);
+ if (BoundedRational.asBigInteger(res.ratVal) == null
&& msd == INVALID_MSD) {
- prec = MAX_MSD_PREC;
- initCache = res.mVal.toString(prec);
- msd = getMsdPos(initCache);
+ precOffset = MAX_MSD_PREC_OFFSET;
+ initResult = res.val.toString(precOffset);
+ msd = getMsdIndexOf(initResult);
}
- int lsd = getLsd(res.mRatVal, initCache, initCache.indexOf('.'));
- int initDisplayPrec = getPreferredPrec(initCache, msd, lsd);
- int newPrec = initDisplayPrec + EXTRA_DIGITS;
- if (newPrec > prec) {
- prec = newPrec;
- initCache = res.mVal.toString(prec);
+ final int lsdOffset = getLsdOffset(res.ratVal, initResult,
+ initResult.indexOf('.'));
+ final int initDisplayOffset = getPreferredPrec(initResult, msd, lsdOffset);
+ final int newPrecOffset = initDisplayOffset + EXTRA_DIGITS;
+ if (newPrecOffset > precOffset) {
+ precOffset = newPrecOffset;
+ initResult = res.val.toString(precOffset);
}
- return new InitialResult(res.mVal, res.mRatVal,
- initCache, prec, initDisplayPrec);
+ return new InitialResult(res.val, res.ratVal,
+ initResult, precOffset, initDisplayOffset);
} catch (CalculatorExpr.SyntaxException e) {
return new InitialResult(R.string.error_syntax);
} catch (BoundedRational.ZeroDivisionException e) {
- // Division by zero caught by BoundedRational;
- // the easy and more common case.
return new InitialResult(R.string.error_zero_divide);
} catch(ArithmeticException e) {
return new InitialResult(R.string.error_nan);
} catch(CR.PrecisionOverflowException e) {
- // Extremely unlikely unless we're actually dividing by
- // zero or the like.
+ // Extremely unlikely unless we're actually dividing by zero or the like.
return new InitialResult(R.string.error_overflow);
} catch(CR.AbortedException e) {
return new InitialResult(R.string.error_aborted);
@@ -434,41 +386,41 @@ class Evaluator {
mEvaluator = null;
mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
if (result.isError()) {
- if (result.mErrorResourceId == R.string.timeout) {
+ if (result.errorResourceId == R.string.timeout) {
if (mRequired) {
displayTimeoutMessage();
}
mCalculator.onCancelled();
} else {
- mCalculator.onError(result.mErrorResourceId);
+ mCalculator.onError(result.errorResourceId);
}
return;
}
- mVal = result.mVal;
- mRatVal = result.mRatVal;
- mCache = result.mNewCache;
- mCacheDigs = result.mNewCacheDigs;
- mLastDigs = result.mInitDisplayPrec;
- int dotPos = mCache.indexOf('.');
- String truncatedWholePart = mCache.substring(0, dotPos);
- // Recheck display precision; it may change, since
- // display dimensions may have been unknow the first time.
- // In that case the initial evaluation precision should have
+ mVal = result.val;
+ mRatVal = result.ratVal;
+ // TODO: If the new result ends in lots of zeroes, and we have a rational result which
+ // is greater than (in absolute value) the result string, we should subtract 1 ulp
+ // from the result string. That will prevent a later change from zeroes to nines. We
+ // know that the correct, rounded-toward-zero result has nines.
+ mResultString = result.newResultString;
+ mResultStringOffset = result.newResultStringOffset;
+ final int dotIndex = mResultString.indexOf('.');
+ String truncatedWholePart = mResultString.substring(0, dotIndex);
+ // Recheck display precision; it may change, since display dimensions may have been
+ // unknow the first time. In that case the initial evaluation precision should have
// been conservative.
- // TODO: Could optimize by remembering display size and
- // checking for change.
- int init_prec = result.mInitDisplayPrec;
- int msd = getMsdPos(mCache);
- int leastDigPos = getLsd(mRatVal, mCache, dotPos);
- int new_init_prec = getPreferredPrec(mCache, msd, leastDigPos);
- if (new_init_prec < init_prec) {
- init_prec = new_init_prec;
+ // TODO: Could optimize by remembering display size and checking for change.
+ int initPrecOffset = result.initDisplayOffset;
+ final int msdIndex = getMsdIndexOf(mResultString);
+ final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+ final int newInitPrecOffset = getPreferredPrec(mResultString, msdIndex, leastDigOffset);
+ if (newInitPrecOffset < initPrecOffset) {
+ initPrecOffset = newInitPrecOffset;
} else {
- // They should be equal. But nothing horrible should
- // happen if they're not. e.g. because
- // CalculatorResult.MAX_WIDTH was too small.
+ // They should be equal. But nothing horrible should happen if they're not. e.g.
+ // because CalculatorResult.MAX_WIDTH was too small.
}
- mCalculator.onEvaluate(init_prec, msd, leastDigPos, truncatedWholePart);
+ mCalculator.onEvaluate(initPrecOffset, msdIndex, leastDigOffset, truncatedWholePart);
}
@Override
protected void onCancelled(InitialResult result) {
@@ -476,82 +428,177 @@ class Evaluator {
mTimeoutHandler.removeCallbacks(mTimeoutRunnable);
if (mRequired && !mQuiet) {
displayCancelledMessage();
- } // Otherwise timeout processing displayed message.
+ } // Otherwise, if mRequired, timeout processing displayed message.
mCalculator.onCancelled();
// Just drop the evaluation; Leave expression displayed.
return;
}
}
+ /**
+ * Check whether a new higher precision result flips previously computed trailing 9s
+ * to zeroes. If so, flip them back. Return the adjusted result.
+ * Assumes newPrecOffset >= oldPrecOffset > 0.
+ * Since our results are accurate to < 1 ulp, this can only happen if the true result
+ * is less than the new result with trailing zeroes, and thus appending 9s to the
+ * old result must also be correct. Such flips are impossible if the newly computed
+ * digits consist of anything other than zeroes.
+ * It is unclear that there are real cases in which this is necessary,
+ * but we have failed to prove there aren't such cases.
+ */
+ @VisibleForTesting
+ static String unflipZeroes(String oldDigs, int oldPrecOffset, String newDigs,
+ int newPrecOffset) {
+ final int oldLen = oldDigs.length();
+ if (oldDigs.charAt(oldLen - 1) != '9') {
+ return newDigs;
+ }
+ final int newLen = newDigs.length();
+ final int precDiff = newPrecOffset - oldPrecOffset;
+ final int oldLastInNew = newLen - 1 - precDiff;
+ if (newDigs.charAt(oldLastInNew) != '0') {
+ return newDigs;
+ }
+ // Earlier digits could not have changed without a 0 to 9 or 9 to 0 flip at end.
+ // The former is OK.
+ if (!newDigs.substring(newLen - precDiff).equals(repeat('0', precDiff))) {
+ throw new AssertionError("New approximation invalidates old one!");
+ }
+ return oldDigs + repeat('9', precDiff);
+ }
- // Start an evaluation to prec, and ensure that the
- // display is redrawn when it completes.
- private void ensureCachePrec(int prec) {
- if (mCache != null && mCacheDigs >= prec
- || mCacheDigsReq >= prec) return;
+ /**
+ * Result of asynchronous reevaluation.
+ */
+ private static class ReevalResult {
+ public final String newResultString;
+ public final int newResultStringOffset;
+ ReevalResult(String s, int p) {
+ newResultString = s;
+ newResultStringOffset = p;
+ }
+ }
+
+ /**
+ * Compute new mResultString contents to prec digits to the right of the decimal point.
+ * Ensure that onReevaluate() is called after doing so. If the evaluation fails for reasons
+ * other than a timeout, ensure that onError() is called.
+ */
+ private class AsyncReevaluator extends AsyncTask<Integer, Void, ReevalResult> {
+ @Override
+ protected ReevalResult doInBackground(Integer... prec) {
+ try {
+ final int precOffset = prec[0].intValue();
+ return new ReevalResult(mVal.toString(precOffset), precOffset);
+ } catch(ArithmeticException e) {
+ return null;
+ } catch(CR.PrecisionOverflowException e) {
+ return null;
+ } catch(CR.AbortedException e) {
+ // Should only happen if the task was cancelled, in which case we don't look at
+ // the result.
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(ReevalResult result) {
+ if (result == null) {
+ // This should only be possible in the extremely rare case of encountering a
+ // domain error while reevaluating or in case of a precision overflow. We don't
+ // know of a way to get the latter with a plausible amount of user input.
+ mCalculator.onError(R.string.error_nan);
+ } else {
+ if (result.newResultStringOffset < mResultStringOffset) {
+ throw new AssertionError("Unexpected onPostExecute timing");
+ }
+ mResultString = unflipZeroes(mResultString, mResultStringOffset,
+ result.newResultString, result.newResultStringOffset);
+ mResultStringOffset = result.newResultStringOffset;
+ mCalculator.onReevaluate();
+ }
+ mCurrentReevaluator = null;
+ }
+ // On cancellation we do nothing; invoker should have left no trace of us.
+ }
+
+ /**
+ * If necessary, start an evaluation to precOffset.
+ * Ensure that the display is redrawn when it completes.
+ */
+ private void ensureCachePrec(int precOffset) {
+ if (mResultString != null && mResultStringOffset >= precOffset
+ || mResultStringOffsetReq >= precOffset) return;
if (mCurrentReevaluator != null) {
// Ensure we only have one evaluation running at a time.
mCurrentReevaluator.cancel(true);
mCurrentReevaluator = null;
}
mCurrentReevaluator = new AsyncReevaluator();
- mCacheDigsReq = prec + PRECOMPUTE_DIGITS;
- if (mCache != null) {
- mCacheDigsReq += mCacheDigsReq / PRECOMPUTE_DIVISOR;
+ mResultStringOffsetReq = precOffset + PRECOMPUTE_DIGITS;
+ if (mResultString != null) {
+ mResultStringOffsetReq += mResultStringOffsetReq / PRECOMPUTE_DIVISOR;
}
- mCurrentReevaluator.execute(mCacheDigsReq);
+ mCurrentReevaluator.execute(mResultStringOffsetReq);
}
/**
* Return the rightmost nonzero digit position, if any.
* @param ratVal Rational value of result or null.
* @param cache Current cached decimal string representation of result.
- * @param decPos Index of decimal point in cache.
+ * @param decIndex Index of decimal point in cache.
* @result Position of rightmost nonzero digit relative to decimal point.
* Integer.MIN_VALUE if ratVal is zero. Integer.MAX_VALUE if there is no lsd,
* or we cannot determine it.
*/
- int getLsd(BoundedRational ratVal, String cache, int decPos) {
+ int getLsdOffset(BoundedRational ratVal, String cache, int decIndex) {
if (ratVal != null && ratVal.signum() == 0) return Integer.MIN_VALUE;
int result = BoundedRational.digitsRequired(ratVal);
if (result == 0) {
int i;
- for (i = -1; decPos + i > 0 && cache.charAt(decPos + i) == '0'; --i) { }
+ for (i = -1; decIndex + i > 0 && cache.charAt(decIndex + i) == '0'; --i) { }
result = i;
}
return result;
}
+ // TODO: We may want to consistently specify the position of the current result
+ // window using the left-most visible digit index instead of the offset for the rightmost one.
+ // It seems likely that would simplify the logic.
+
/**
- * Retrieve the preferred precision for the currently displayed result.
+ * Retrieve the preferred precision "offset" for the currently displayed result.
* May be called from non-UI thread.
* @param cache Current approximation as string.
* @param msd Position of most significant digit in result. Index in cache.
* Can be INVALID_MSD if we haven't found it yet.
- * @param lastDigit Position of least significant digit (1 = tenths digit)
+ * @param lastDigitOffset Position of least significant digit (1 = tenths digit)
* or Integer.MAX_VALUE.
*/
- int getPreferredPrec(String cache, int msd, int lastDigit) {
- int lineLength = mResult.getMaxChars();
- int wholeSize = cache.indexOf('.');
- int negative = cache.charAt(0) == '-' ? 1 : 0;
+ private int getPreferredPrec(String cache, int msd, int lastDigitOffset) {
+ final int lineLength = mResult.getMaxChars();
+ final int wholeSize = cache.indexOf('.');
+ final int negative = cache.charAt(0) == '-' ? 1 : 0;
// Don't display decimal point if result is an integer.
- if (lastDigit == 0) lastDigit = -1;
- if (lastDigit != Integer.MAX_VALUE) {
- if (wholeSize <= lineLength && lastDigit <= 0) {
+ if (lastDigitOffset == 0) {
+ lastDigitOffset = -1;
+ }
+ if (lastDigitOffset != Integer.MAX_VALUE) {
+ if (wholeSize <= lineLength && lastDigitOffset <= 0) {
// Exact integer. Prefer to display as integer, without decimal point.
return -1;
}
- if (lastDigit >= 0 && wholeSize + lastDigit + 1 /* dec.pt. */ <= lineLength) {
+ if (lastDigitOffset >= 0
+ && wholeSize + lastDigitOffset + 1 /* decimal pt. */ <= lineLength) {
// Display full exact number wo scientific notation.
- return lastDigit;
+ return lastDigitOffset;
}
}
if (msd > wholeSize && msd <= wholeSize + EXP_COST + 1) {
// Display number without scientific notation. Treat leading zero as msd.
msd = wholeSize - 1;
}
- if (msd > wholeSize + MAX_MSD_PREC) {
+ if (msd > wholeSize + MAX_MSD_PREC_OFFSET) {
// Display a probable but uncertain 0 as "0.000000000",
// without exponent. That's a judgment call, but less likely
// to confuse naive users. A more informative and confusing
@@ -576,10 +623,10 @@ class Evaluator {
* that if it doesn't contain enough significant digits, we can
* reasonably abbreviate as SHORT_UNCERTAIN_ZERO.
* @param msdIndex Index of most significant digit in cache, or INVALID_MSD.
- * @param lsd Position of least significant digit in finite representation,
+ * @param lsdOffset Position of least significant digit in finite representation,
* relative to decimal point, or MAX_VALUE.
*/
- private String getShortString(String cache, int msdIndex, int lsd) {
+ private String getShortString(String cache, int msdIndex, int lsdOffset) {
// This somewhat mirrors the display formatting code, but
// - The constants are different, since we don't want to use the whole display.
// - This is an easier problem, since we don't support scrolling and the length
@@ -594,7 +641,7 @@ class Evaluator {
msdIndex = INVALID_MSD;
}
if (msdIndex == INVALID_MSD) {
- if (lsd < INIT_PREC) {
+ if (lsdOffset < INIT_PREC) {
return "0";
} else {
return SHORT_UNCERTAIN_ZERO;
@@ -602,19 +649,19 @@ class Evaluator {
}
// Avoid scientific notation for small numbers of zeros.
// Instead stretch significant digits to include decimal point.
- if (lsd < -1 && dotIndex - msdIndex + negative <= SHORT_TARGET_LENGTH
- && lsd >= -CalculatorResult.MAX_TRAILING_ZEROES - 1) {
+ if (lsdOffset < -1 && dotIndex - msdIndex + negative <= SHORT_TARGET_LENGTH
+ && lsdOffset >= -CalculatorResult.MAX_TRAILING_ZEROES - 1) {
// Whole number that fits in allotted space.
// CalculatorResult would not use scientific notation either.
- lsd = -1;
+ lsdOffset = -1;
}
if (msdIndex > dotIndex) {
if (msdIndex <= dotIndex + EXP_COST + 1) {
// Preferred display format inthis cases is with leading zeroes, even if
// it doesn't fit entirely. Replicate that here.
msdIndex = dotIndex - 1;
- } else if (lsd <= SHORT_TARGET_LENGTH - negative - 2
- && lsd <= CalculatorResult.MAX_LEADING_ZEROES + 1) {
+ } else if (lsdOffset <= SHORT_TARGET_LENGTH - negative - 2
+ && lsdOffset <= CalculatorResult.MAX_LEADING_ZEROES + 1) {
// Fraction that fits entirely in allotted space.
// CalculatorResult would not use scientific notation either.
msdIndex = dotIndex -1;
@@ -625,10 +672,10 @@ class Evaluator {
// Adjust for the fact that the decimal point itself takes space.
exponent--;
}
- if (lsd != Integer.MAX_VALUE) {
- int lsdIndex = dotIndex + lsd;
- int totalDigits = lsdIndex - msdIndex + negative + 1;
- if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsd >= -1) {
+ if (lsdOffset != Integer.MAX_VALUE) {
+ final int lsdIndex = dotIndex + lsdOffset;
+ final int totalDigits = lsdIndex - msdIndex + negative + 1;
+ if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsdOffset >= -1) {
// Fits, no exponent needed.
return negativeSign + cache.substring(msdIndex, lsdIndex + 1);
}
@@ -648,183 +695,182 @@ class Evaluator {
+ KeyMaps.ELLIPSIS + "E" + exponent;
}
- // Return the most significant digit position in the given string
- // or INVALID_MSD.
- public static int getMsdPos(String s) {
- int len = s.length();
- int nonzeroPos = -1;
+ /**
+ * Return the most significant digit index in the given numeric string.
+ * Return INVALID_MSD if there are not enough digits to prove the numeric value is
+ * different from zero. As usual, we assume an error of strictly less than 1 ulp.
+ */
+ public static int getMsdIndexOf(String s) {
+ final int len = s.length();
+ int nonzeroIndex = -1;
for (int i = 0; i < len; ++i) {
char c = s.charAt(i);
if (c != '-' && c != '.' && c != '0') {
- nonzeroPos = i;
+ nonzeroIndex = i;
break;
}
}
- if (nonzeroPos >= 0 &&
- (nonzeroPos < len - 1 || s.charAt(nonzeroPos) != '1')) {
- return nonzeroPos;
+ if (nonzeroIndex >= 0 && (nonzeroIndex < len - 1 || s.charAt(nonzeroIndex) != '1')) {
+ return nonzeroIndex;
} else {
- // Unknown, or could change on reevaluation
return INVALID_MSD;
}
}
- // Return most significant digit position in the cache, if determined,
- // INVALID_MSD ow.
- // If unknown, and we've computed less than DESIRED_PREC,
- // schedule reevaluation and redisplay, with higher precision.
- int getMsd() {
- if (mMsd != INVALID_MSD) return mMsd;
+ /**
+ * Return most significant digit index in the currently computed result.
+ * Returns an index in the result character array. Return INVALID_MSD if the current result
+ * is too close to zero to determine the result.
+ * Result is almost consistent through reevaluations: It may increase by one, once.
+ */
+ private int getMsdIndex() {
+ if (mMsdIndex != INVALID_MSD) {
+ // 0.100000... can change to 0.0999999... We may have to correct once by one digit.
+ if (mResultString.charAt(mMsdIndex) == '0') {
+ mMsdIndex++;
+ }
+ return mMsdIndex;
+ }
if (mRatVal != null && mRatVal.signum() == 0) {
return INVALID_MSD; // None exists
}
- int res = INVALID_MSD;
- if (mCache != null) {
- res = getMsdPos(mCache);
+ int result = INVALID_MSD;
+ if (mResultString != null) {
+ result = getMsdIndexOf(mResultString);
}
- if (res == INVALID_MSD && mEvaluator == null
- && mCurrentReevaluator == null && mCacheDigs < MAX_MSD_PREC) {
- // We assert that mCache is not null, since there is no
- // evaluator running.
- ensureCachePrec(MAX_MSD_PREC);
- // Could reevaluate more incrementally, but we suspect that if
- // we have to reevaluate at all, the result is probably zero.
- }
- return res;
+ return result;
}
- // Return a string with n placeholder characters.
- private String getPadding(int n) {
- StringBuilder padding = new StringBuilder();
+ /**
+ * Return a string with n copies of c.
+ */
+ private static String repeat(char c, int n) {
+ StringBuilder result = new StringBuilder();
for (int i = 0; i < n; ++i) {
- padding.append(' '); // To be replaced during final translation.
+ result.append(c);
}
- return padding.toString();
+ return result.toString();
}
- // Return the number of zero characters at the beginning of s
- private int leadingZeroes(String s) {
- int res = 0;
- int len = s.length();
- for (res = 0; res < len && s.charAt(res) == '0'; ++res) {}
- return res;
- }
+ // Refuse to scroll past the point at which this many digits from the whole number
+ // part of the result are still displayed. Avoids sily displays like 1E1.
+ private static final int MIN_DISPLAYED_DIGS = 5;
- private static final int MIN_DIGS = 5;
- // Leave at least this many digits from the whole number
- // part on the screen, to avoid silly displays like 1E1.
- // Return result to exactly prec[0] digits to the right of the
- // decimal point.
- // The result should be no longer than maxDigs.
- // No exponent or other indication of precision is added.
- // The result is returned immediately, based on the
- // current cache contents, but it may contain question
- // marks for unknown digits. It may also use uncertain
- // digits within EXTRA_DIGITS. If either of those occurred,
- // schedule a reevaluation and redisplay operation.
- // Uncertain digits never appear to the left of the decimal point.
- // digs may be negative to only retrieve digits to the left
- // of the decimal point. (prec[0] = 0 means we include
- // the decimal point, but nothing to the right. prec[0] = -1
- // means we drop the decimal point and start at the ones
- // position. Should not be invoked if mVal is null.
- // This essentially just returns a substring of the full result;
- // a leading minus sign or leading digits can be dropped.
- // Result uses US conventions; is NOT internationalized.
- // We set negative[0] if the number as a whole is negative,
- // since we may drop the minus sign.
- // We set truncated[0] if leading nonzero digits were dropped.
- // getRational() can be used to determine whether the result
- // is exact, or whether we dropped trailing digits.
- // If the requested prec[0] value is out of range, we update
- // it in place and use the updated value. But we do not make it
- // greater than maxPrec.
- public String getString(int[] prec, int maxPrec, int maxDigs,
- boolean[] truncated, boolean[] negative) {
- int digs = prec[0];
- mLastDigs = digs;
+ /**
+ * Return result to precOffset[0] digits to the right of the decimal point.
+ * PrecOffset[0] is updated if the original value is out of range. No exponent or other
+ * indication of precision is added. The result is returned immediately, based on the current
+ * cache contents, but it may contain question marks for unknown digits. It may also use
+ * uncertain digits within EXTRA_DIGITS. If either of those occurred, schedule a reevaluation
+ * and redisplay operation. Uncertain digits never appear to the left of the decimal point.
+ * PrecOffset[0] may be negative to only retrieve digits to the left of the decimal point.
+ * (precOffset[0] = 0 means we include the decimal point, but nothing to the right.
+ * precOffset[0] = -1 means we drop the decimal point and start at the ones position. Should
+ * not be invoked before the onEvaluate() callback is received. This essentially just returns
+ * a substring of the full result; a leading minus sign or leading digits can be dropped.
+ * Result uses US conventions; is NOT internationalized. Use getRational() to determine
+ * whether the result is exact, or whether we dropped trailing digits.
+ *
+ * @param precOffset Zeroth element indicates desired and actual precision
+ * @param maxPrecOffset Maximum adjusted precOffset[0]
+ * @param maxDigs Maximum length of result
+ * @param truncated Zeroth element is set if leading nonzero digits were dropped
+ * @param negative Zeroth element is set of the result is negative.
+ */
+ public String getString(int[] precOffset, int maxPrecOffset, int maxDigs, boolean[] truncated,
+ boolean[] negative) {
+ int currentPrecOffset = precOffset[0];
// Make sure we eventually get a complete answer
- if (mCache == null) {
- ensureCachePrec(digs + EXTRA_DIGITS);
- // Nothing else to do now; seems to happen on rare occasion
- // with weird user input timing;
- // Will repair itself in a jiffy.
- return getPadding(1);
- } else {
- ensureCachePrec(digs + EXTRA_DIGITS
- + mCache.length() / EXTRA_DIVISOR);
- }
- // Compute an appropriate substring of mCache.
- // We avoid returning a huge string to minimize string
- // allocation during scrolling.
- // Pad as needed.
- final int len = mCache.length();
- final boolean myNegative = mCache.charAt(0) == '-';
- negative[0] = myNegative;
- // Don't scroll left past leftmost digits in mCache
- // unless that still leaves an integer.
- int integralDigits = len - mCacheDigs;
- // includes 1 for dec. pt
- if (myNegative) --integralDigits;
- int minDigs = Math.min(-integralDigits + MIN_DIGS, -1);
- digs = Math.min(Math.max(digs, minDigs), maxPrec);
- prec[0] = digs;
- int offset = mCacheDigs - digs; // trailing digits to drop
- int deficit = 0; // The number of digits we're short
- if (offset < 0) {
- offset = 0;
- deficit = Math.min(digs - mCacheDigs, maxDigs);
- }
- int endIndx = len - offset;
- if (endIndx < 1) return " ";
- int startIndx = (endIndx + deficit <= maxDigs) ?
- 0
- : endIndx + deficit - maxDigs;
- truncated[0] = (startIndx > getMsd());
- String res = mCache.substring(startIndx, endIndx);
- if (deficit > 0) {
- res = res + getPadding(deficit);
- // Since we always compute past the decimal point,
- // this never fills in the spot where the decimal point
- // should go, and the rest of this can treat the
- // made-up symbols as though they were digits.
+ if (mResultString == null) {
+ ensureCachePrec(currentPrecOffset + EXTRA_DIGITS);
+ // Nothing else to do now; seems to happen on rare occasion with weird user input
+ // timing; Will repair itself in a jiffy.
+ return " ";
+ } else {
+ ensureCachePrec(currentPrecOffset + EXTRA_DIGITS + mResultString.length()
+ / EXTRA_DIVISOR);
+ }
+ // Compute an appropriate substring of mResultString. Pad if necessary.
+ final int len = mResultString.length();
+ final boolean myNegative = mResultString.charAt(0) == '-';
+ negative[0] = myNegative;
+ // Don't scroll left past leftmost digits in mResultString unless that still leaves an
+ // integer.
+ int integralDigits = len - mResultStringOffset;
+ // includes 1 for dec. pt
+ if (myNegative) {
+ --integralDigits;
}
- return res;
+ int minPrecOffset = Math.min(MIN_DISPLAYED_DIGS - integralDigits, -1);
+ currentPrecOffset = Math.min(Math.max(currentPrecOffset, minPrecOffset),
+ maxPrecOffset);
+ precOffset[0] = currentPrecOffset;
+ int extraDigs = mResultStringOffset - currentPrecOffset; // trailing digits to drop
+ int deficit = 0; // The number of digits we're short
+ if (extraDigs < 0) {
+ extraDigs = 0;
+ deficit = Math.min(currentPrecOffset - mResultStringOffset, maxDigs);
+ }
+ int endIndex = len - extraDigs;
+ if (endIndex < 1) {
+ return " ";
+ }
+ int startIndex = Math.max(endIndex + deficit - maxDigs, 0);
+ truncated[0] = (startIndex > getMsdIndex());
+ String result = mResultString.substring(startIndex, endIndex);
+ if (deficit > 0) {
+ result += repeat(' ', deficit);
+ // Blank character is replaced during translation.
+ // Since we always compute past the decimal point, this never fills in the spot
+ // where the decimal point should go, and we can otherwise treat placeholders
+ // as though they were digits.
+ }
+ return result;
}
- // Return rational representation of current result, if any.
+ /**
+ * Return rational representation of current result, if any.
+ * Return null if the result is irrational, or we couldn't track the rational value,
+ * e.g. because the denominator got too big.
+ */
public BoundedRational getRational() {
return mRatVal;
}
private void clearCache() {
- mCache = null;
- mCacheDigs = mCacheDigsReq = 0;
- mMsd = INVALID_MSD;
+ mResultString = null;
+ mResultStringOffset = mResultStringOffsetReq = 0;
+ mMsdIndex = INVALID_MSD;
}
- void clear() {
+
+ private void clearPreservingTimeout() {
mExpr.clear();
clearCache();
}
+ public void clear() {
+ clearPreservingTimeout();
+ mLongTimeout = false;
+ }
+
/**
* Start asynchronous result evaluation of formula.
* Will result in display on completion.
* @param required result was explicitly requested by user.
*/
- private void reevaluateResult(boolean required) {
+ private void evaluateResult(boolean required) {
clearCache();
- mEvaluator = new AsyncDisplayResult(mDegreeMode, required);
+ mEvaluator = new AsyncEvaluator(mDegreeMode, required);
mEvaluator.execute();
mChangedValue = false;
}
- // Begin evaluation of result and display when ready.
- // We assume this is called after each insertion and deletion.
- // Thus if we are called twice with the same effective end of
- // the formula, the evaluation is redundant.
- void evaluateAndShowResult() {
+ /**
+ * Start optional evaluation of result and display when ready.
+ * Can quietly time out without a user-visible display.
+ */
+ public void evaluateAndShowResult() {
if (!mChangedValue) {
// Already done or in progress.
return;
@@ -832,25 +878,27 @@ class Evaluator {
// In very odd cases, there can be significant latency to evaluate.
// Don't show obsolete result.
mResult.clear();
- reevaluateResult(false);
+ evaluateResult(false);
}
- // Ensure that we either display a result or complain.
- // Does not invalidate a previously computed cache.
- // We presume that any prior result was computed using the same
- // expression.
- void requireResult() {
- if (mCache == null || mChangedValue) {
+ /**
+ * Start required evaluation of result and display when ready.
+ * Will eventually call back mCalculator to display result or error, or display
+ * a timeout message. Uses longer timeouts than optional evaluation.
+ */
+ public void requireResult() {
+ if (mResultString == null || mChangedValue) {
// Restart evaluator in requested mode, i.e. with longer timeout.
cancelAll(true);
- reevaluateResult(true);
+ evaluateResult(true);
} else {
// Notify immediately, reusing existing result.
- int dotPos = mCache.indexOf('.');
- String truncatedWholePart = mCache.substring(0, dotPos);
- int leastDigOffset = getLsd(mRatVal, mCache, dotPos);
- int msdIndex = getMsd();
- int preferredPrecOffset = getPreferredPrec(mCache, msdIndex, leastDigOffset);
+ final int dotIndex = mResultString.indexOf('.');
+ final String truncatedWholePart = mResultString.substring(0, dotIndex);
+ final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+ final int msdIndex = getMsdIndex();
+ final int preferredPrecOffset = getPreferredPrec(mResultString, msdIndex,
+ leastDigOffset);
mCalculator.onEvaluate(preferredPrecOffset, msdIndex, leastDigOffset,
truncatedWholePart);
}
@@ -861,10 +909,10 @@ class Evaluator {
* @param quiet suppress cancellation message
* @return true if we cancelled an initial evaluation
*/
- boolean cancelAll(boolean quiet) {
+ public boolean cancelAll(boolean quiet) {
if (mCurrentReevaluator != null) {
mCurrentReevaluator.cancel(true);
- mCacheDigsReq = mCacheDigs;
+ mResultStringOffsetReq = mResultStringOffset;
// Backgound computation touches only constructive reals.
// OK not to wait.
mCurrentReevaluator = null;
@@ -888,11 +936,16 @@ class Evaluator {
return false;
}
- void restoreInstanceState(DataInput in) {
+ /**
+ * Restore the evaluator state, including the expression and any saved value.
+ */
+ public void restoreInstanceState(DataInput in) {
mChangedValue = true;
try {
CalculatorExpr.initExprInput();
mDegreeMode = in.readBoolean();
+ mLongTimeout = in.readBoolean();
+ mLongSavedTimeout = in.readBoolean();
mExpr = new CalculatorExpr(in);
mSavedName = in.readUTF();
mSaved = new CalculatorExpr(in);
@@ -901,10 +954,15 @@ class Evaluator {
}
}
- void saveInstanceState(DataOutput out) {
+ /**
+ * Save the evaluator state, including the expression and any saved value.
+ */
+ public void saveInstanceState(DataOutput out) {
try {
CalculatorExpr.initExprOutput();
out.writeBoolean(mDegreeMode);
+ out.writeBoolean(mLongTimeout);
+ out.writeBoolean(mLongSavedTimeout);
mExpr.write(out);
out.writeUTF(mSavedName);
mSaved.write(out);
@@ -913,11 +971,14 @@ class Evaluator {
}
}
- // Append a button press to the current expression.
- // Return false if we rejected the insertion due to obvious
- // syntax issues, and the expression is unchanged.
- // Return true otherwise.
- boolean append(int id) {
+
+ /**
+ * Append a button press to the current expression.
+ * @param id Button identifier for the character or operator to be added.
+ * @return false if we rejected the insertion due to obvious syntax issues, and the expression
+ * is unchanged; true otherwise
+ */
+ public boolean append(int id) {
if (id == R.id.fun_10pow) {
add10pow(); // Handled as macro expansion.
return true;
@@ -927,9 +988,12 @@ class Evaluator {
}
}
- void delete() {
+ public void delete() {
mChangedValue = true;
mExpr.delete();
+ if (mExpr.isEmpty()) {
+ mLongTimeout = false;
+ }
}
void setDegreeMode(boolean degreeMode) {
@@ -946,79 +1010,82 @@ class Evaluator {
}
/**
- * @return the {@link CalculatorExpr} representation of the current result
+ * @return the {@link CalculatorExpr} representation of the current result.
*/
- CalculatorExpr getResultExpr() {
- final int dotPos = mCache.indexOf('.');
- final int leastDigPos = getLsd(mRatVal, mCache, dotPos);
+ private CalculatorExpr getResultExpr() {
+ final int dotIndex = mResultString.indexOf('.');
+ final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
return mExpr.abbreviate(mVal, mRatVal, mDegreeMode,
- getShortString(mCache, getMsdPos(mCache), leastDigPos));
+ getShortString(mResultString, getMsdIndexOf(mResultString), leastDigOffset));
}
- // Abbreviate the current expression to a pre-evaluated
- // expression node, which will display as a short number.
- // This should not be called unless the expression was
- // previously evaluated and produced a non-error result.
- // Pre-evaluated expressions can never represent an
- // expression for which evaluation to a constructive real
- // diverges. Subsequent re-evaluation will also not diverge,
- // though it may generate errors of various kinds.
- // E.g. sqrt(-10^-1000)
- void collapse() {
+ /**
+ * Abbreviate the current expression to a pre-evaluated expression node.
+ * This should not be called unless the expression was previously evaluated and produced a
+ * non-error result. Pre-evaluated expressions can never represent an expression for which
+ * evaluation to a constructive real diverges. Subsequent re-evaluation will also not
+ * diverge, though it may generate errors of various kinds. E.g. sqrt(-10^-1000) .
+ */
+ public void collapse() {
final CalculatorExpr abbrvExpr = getResultExpr();
- clear();
+ clearPreservingTimeout();
mExpr.append(abbrvExpr);
mChangedValue = true;
}
- // Same as above, but put result in mSaved, leaving mExpr alone.
- // Return false if result is unavailable.
- boolean collapseToSaved() {
- if (mCache == null) {
+ /**
+ * Abbreviate current expression, and put result in mSaved.
+ * mExpr is left alone. Return false if result is unavailable.
+ */
+ public boolean collapseToSaved() {
+ if (mResultString == null) {
return false;
}
-
final CalculatorExpr abbrvExpr = getResultExpr();
mSaved.clear();
mSaved.append(abbrvExpr);
+ mLongSavedTimeout = mLongTimeout;
return true;
}
- Uri uriForSaved() {
+ private Uri uriForSaved() {
return new Uri.Builder().scheme("tag")
.encodedOpaquePart(mSavedName)
.build();
}
- // Collapse the current expression to mSaved and return a URI
- // describing this particular result, so that we can refer to it
- // later.
- Uri capture() {
+ /**
+ * Collapse the current expression to mSaved and return a URI describing it.
+ * describing this particular result, so that we can refer to it
+ * later.
+ */
+ public Uri capture() {
if (!collapseToSaved()) return null;
// Generate a new (entirely private) URI for this result.
// Attempt to conform to RFC4151, though it's unclear it matters.
- Date date = new Date();
- TimeZone tz = TimeZone.getDefault();
+ final TimeZone tz = TimeZone.getDefault();
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
df.setTimeZone(tz);
- String isoDate = df.format(new Date());
+ final String isoDate = df.format(new Date());
mSavedName = "calculator2.android.com," + isoDate + ":"
- + (new Random().nextInt() & 0x3fffffff);
- Uri tag = uriForSaved();
- return tag;
+ + (new Random().nextInt() & 0x3fffffff);
+ return uriForSaved();
}
- boolean isLastSaved(Uri uri) {
+ public boolean isLastSaved(Uri uri) {
return uri.equals(uriForSaved());
}
- void addSaved() {
+ public void appendSaved() {
mChangedValue = true;
+ mLongTimeout |= mLongSavedTimeout;
mExpr.append(mSaved);
}
- // Add the power of 10 operator to the expression. This is treated
- // essentially as a macro expansion.
+ /**
+ * Add the power of 10 operator to the expression.
+ * This is treated essentially as a macro expansion.
+ */
private void add10pow() {
CalculatorExpr ten = new CalculatorExpr();
ten.add(R.id.digit_1);
@@ -1028,24 +1095,31 @@ class Evaluator {
mExpr.add(R.id.op_pow);
}
- // Retrieve the main expression being edited.
- // It is the callee's reponsibility to call cancelAll to cancel
- // ongoing concurrent computations before modifying the result.
- // TODO: Perhaps add functionality so we can keep this private?
- CalculatorExpr getExpr() {
+ /**
+ * Retrieve the main expression being edited.
+ * It is the callee's reponsibility to call cancelAll to cancel ongoing concurrent
+ * computations before modifying the result. The resulting expression should only
+ * be modified by the caller if either the expression value doesn't change, or in
+ * combination with another add() or delete() call that makes the value change apparent
+ * to us.
+ * TODO: Perhaps add functionality so we can keep this private?
+ */
+ public CalculatorExpr getExpr() {
return mExpr;
}
+ /**
+ * Maximum number of characters in a scientific notation exponent.
+ */
private static final int MAX_EXP_CHARS = 8;
/**
* Return the index of the character after the exponent starting at s[offset].
* Return offset if there is no exponent at that position.
- * Exponents have syntax E[-]digit* .
- * "E2" and "E-2" are valid. "E+2" and "e2" are not.
+ * Exponents have syntax E[-]digit* . "E2" and "E-2" are valid. "E+2" and "e2" are not.
* We allow any Unicode digits, and either of the commonly used minus characters.
*/
- static int exponentEnd(String s, int offset) {
+ public static int exponentEnd(String s, int offset) {
int i = offset;
int len = s.length();
if (i >= len - 1 || s.charAt(i) != 'E') {
@@ -1055,12 +1129,15 @@ class Evaluator {
if (KeyMaps.keyForChar(s.charAt(i)) == R.id.op_sub) {
++i;
}
- if (i == len || i > offset + MAX_EXP_CHARS || !Character.isDigit(s.charAt(i))) {
+ if (i == len || !Character.isDigit(s.charAt(i))) {
return offset;
}
++i;
while (i < len && Character.isDigit(s.charAt(i))) {
++i;
+ if (i > offset + MAX_EXP_CHARS) {
+ return offset;
+ }
}
return i;
}
@@ -1068,10 +1145,10 @@ class Evaluator {
/**
* Add the exponent represented by s[begin..end) to the constant at the end of current
* expression.
- * The end of the current expression must be a constant.
- * Exponents have the same syntax as for exponentEnd().
+ * The end of the current expression must be a constant. Exponents have the same syntax as
+ * for exponentEnd().
*/
- void addExponent(String s, int begin, int end) {
+ public void addExponent(String s, int begin, int end) {
int sign = 1;
int exp = 0;
int i = begin + 1;
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 62fe5c0..491603d 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
<instrumentation
android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="com.android.exactcalculator"
+ android:targetPackage="com.android.calculator2"
android:label="BoundedRational and Calculator Functional Test" />
<application>
diff --git a/tests/README.txt b/tests/README.txt
index ee64f0c..bfe35ca 100644
--- a/tests/README.txt
+++ b/tests/README.txt
@@ -4,9 +4,9 @@ Run on Android with
2) Install the calculator with
adb install <tree root>/out/target/product/generic/data/app/ExactCalculator/ExactCalculator.apk
3) adb install <tree root>/out/target/product/generic/data/app/ExactCalculatorTests/ExactCalculatorTests.apk
-4) adb shell am instrument -w com.android.exactcalculator.tests/android.test.InstrumentationTestRunner
+4) adb shell am instrument -w com.android.calculator2.tests/android.test.InstrumentationTestRunner
-There are two kinds of tests:
+There are three kinds of tests:
1. A superficial test of calculator functionality through the UI.
This is a resurrected version of a test that appeared in KitKat.
@@ -20,6 +20,9 @@ terminating decimal expansions. But it's also used to optimize CR
computations, and bugs in BoundedRational could result in incorrect
outputs.)
+3. A quick test of Evaluator.testUnflipZeroes(), which we do not know how to
+test manually.
+
We currently have no automatic tests for display formatting corner cases.
The following numbers have exhibited problems in the past and would be good
to test. Some of them are difficult to test automatically, because they
@@ -27,7 +30,7 @@ require scrolling to both ends of the result. For those with finite
decimal expansions, it also worth confirming that the "display with leading
digits" display shows an exact value when scrolled all the way to the right.
-Some interesting test cases:
+Some interesting manual test cases:
10^10 + 10^30
10^30 + 10^-10
@@ -36,6 +39,8 @@ Some interesting test cases:
-10^30 - 10^10
-1.2x10^-9
-1.2x10^-8
+-1.2x10^-10
+-10^-12
1 - 10^-98
1 - 10^-100
1 - 10^-300
diff --git a/tests/src/com/android/calculator2/BRTest.java b/tests/src/com/android/calculator2/BRTest.java
index 77d4bf0..68214d0 100644
--- a/tests/src/com/android/calculator2/BRTest.java
+++ b/tests/src/com/android/calculator2/BRTest.java
@@ -139,12 +139,22 @@ public class BRTest extends TestCase {
check(BR_0.signum() == 0, "signum(0)");
check(BR_M1.signum() == -1, "signum(-1)");
check(BR_2.signum() == 1, "signum(2)");
+ check(BoundedRational.asBigInteger(BR_390).intValue() == 390, "390.asBigInteger()");
+ check(BoundedRational.asBigInteger(BoundedRational.HALF) == null, "1/2.asBigInteger()");
+ check(BoundedRational.asBigInteger(BoundedRational.MINUS_HALF) == null,
+ "-1/2.asBigInteger()");
+ check(BoundedRational.asBigInteger(new BoundedRational(15, -5)).intValue() == -3,
+ "-15/5.asBigInteger()");
check(BoundedRational.digitsRequired(BoundedRational.ZERO) == 0, "digitsRequired(0)");
check(BoundedRational.digitsRequired(BoundedRational.HALF) == 1, "digitsRequired(1/2)");
check(BoundedRational.digitsRequired(BoundedRational.MINUS_HALF) == 1,
"digitsRequired(-1/2)");
check(BoundedRational.digitsRequired(new BoundedRational(1,-2)) == 1,
"digitsRequired(1/-2)");
+ check(BoundedRational.fact(BoundedRational.ZERO).equals(BoundedRational.ONE), "0!");
+ check(BoundedRational.fact(BoundedRational.ONE).equals(BoundedRational.ONE), "1!");
+ check(BoundedRational.fact(BoundedRational.TWO).equals(BoundedRational.TWO), "2!");
+ check(BoundedRational.fact(BR_15).equals(new BoundedRational(1307674368000L)), "15!");
// We check values that include all interesting degree values.
BoundedRational r = BR_M390;
while (!r.equals(BR_390)) {
diff --git a/tests/src/com/android/calculator2/EvaluatorTest.java b/tests/src/com/android/calculator2/EvaluatorTest.java
new file mode 100644
index 0000000..307aef2
--- /dev/null
+++ b/tests/src/com/android/calculator2/EvaluatorTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calculator2;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+/**
+ * A test for a few static methods in Evaluator.
+ * The most interesting one is for unflipZeroes(), which we don't know how to test with
+ * real calculator input.
+ */
+public class EvaluatorTest extends TestCase {
+ private static void check(boolean x, String s) {
+ if (!x) throw new AssertionFailedError(s);
+ }
+ public void testUnflipZeroes() {
+ check(Evaluator.unflipZeroes("9.99", 2, "9.998", 3).equals("9.998"), "test 1");
+ check(Evaluator.unflipZeroes("9.99", 2, "10.0000", 4).equals("9.9999"), "test 2");
+ check(Evaluator.unflipZeroes("0.99", 2, "1.00000", 5).equals("0.99999"), "test 3");
+ check(Evaluator.unflipZeroes("0.99", 2, "1.00", 2).equals("0.99"), "test 4");
+ check(Evaluator.unflipZeroes("10.00", 2, "9.9999", 4).equals("9.9999"), "test 5");
+ check(Evaluator.unflipZeroes("-10.00", 2, "-9.9999", 4).equals("-9.9999"), "test 6");
+ check(Evaluator.unflipZeroes("-0.99", 2, "-1.00000000000000", 14)
+ .equals("-0.99999999999999"), "test 7");
+ check(Evaluator.unflipZeroes("12349.99", 2, "12350.00000", 5).equals("12349.99999"),
+ "test 8");
+ check(Evaluator.unflipZeroes("123.4999", 4, "123.5000000", 7).equals("123.4999999"),
+ "test 9");
+ }
+
+ public void testGetMsdIndexOf() {
+ check(Evaluator.getMsdIndexOf("-0.0234") == 4, "getMsdIndexOf(-0.0234)");
+ check(Evaluator.getMsdIndexOf("23.45") == 0, "getMsdIndexOf(23.45)");
+ check(Evaluator.getMsdIndexOf("-0.01") == Evaluator.INVALID_MSD, "getMsdIndexOf(-0.01)");
+ }
+
+ public void testExponentEnd() {
+ check(Evaluator.exponentEnd("xE-2%3", 1) == 4, "exponentEnd(xE-2%3)");
+ check(Evaluator.exponentEnd("xE+2%3", 1) == 1, "exponentEnd(xE+2%3)");
+ check(Evaluator.exponentEnd("xe2%3", 1) == 1, "exponentEnd(xe2%3)");
+ check(Evaluator.exponentEnd("xE123%3", 1) == 5, "exponentEnd(xE123%3)");
+ check(Evaluator.exponentEnd("xE123456789%3", 1) == 1, "exponentEnd(xE123456789%3)");
+ }
+}