blob: 2602f563072f1081c5a802bbd0ffec6b4181aff8 [file] [log] [blame]
Hans Boehm682ff5e2015-03-09 14:40:25 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Hans Boehm682ff5e2015-03-09 14:40:25 -070017package com.android.calculator2;
18
19// We implement rational numbers of bounded size.
20// If the length of the nuumerator plus the length of the denominator
21// exceeds a maximum size, we simply return null, and rely on our caller
22// do something else.
23// We currently never return null for a pure integer.
24// TODO: Reconsider that. With some care, large factorials might
25// become much faster.
26//
27// We also implement a number of irrational functions. These return
28// a non-null result only when the result is known to be rational.
29
30import java.math.BigInteger;
31import com.hp.creals.CR;
Hans Boehmc023b732015-04-29 11:30:47 -070032import com.hp.creals.AbortedError;
Hans Boehm682ff5e2015-03-09 14:40:25 -070033
34public class BoundedRational {
35 // TODO: Maybe eventually make this extend Number?
Hans Boehm50ed3202015-06-09 14:35:49 -070036 private static final int MAX_SIZE = 800; // total, in bits
Hans Boehm682ff5e2015-03-09 14:40:25 -070037
38 private final BigInteger mNum;
39 private final BigInteger mDen;
40
41 public BoundedRational(BigInteger n, BigInteger d) {
42 mNum = n;
43 mDen = d;
44 }
45
46 public BoundedRational(BigInteger n) {
47 mNum = n;
48 mDen = BigInteger.ONE;
49 }
50
51 public BoundedRational(long n, long d) {
52 mNum = BigInteger.valueOf(n);
53 mDen = BigInteger.valueOf(d);
54 }
55
56 public BoundedRational(long n) {
57 mNum = BigInteger.valueOf(n);
58 mDen = BigInteger.valueOf(1);
59 }
60
61 // Debug or log messages only, not pretty.
Hans Boehm75ca21c2015-03-11 18:43:24 -070062 public String toString() {
63 return mNum.toString() + "/" + mDen.toString();
64 }
65
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070066 // Output to user, more expensive, less useful for debugging
Hans Boehm013969e2015-04-13 20:29:47 -070067 // Not internationalized.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070068 public String toNiceString() {
Hans Boehm9e855e82015-04-22 18:03:28 -070069 BoundedRational nicer = reduce().positiveDen();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070070 String result = nicer.mNum.toString();
71 if (!nicer.mDen.equals(BigInteger.ONE)) {
72 result += "/" + nicer.mDen;
73 }
74 return result;
75 }
76
Hans Boehm682ff5e2015-03-09 14:40:25 -070077 public static String toString(BoundedRational r) {
78 if (r == null) return "not a small rational";
Hans Boehm75ca21c2015-03-11 18:43:24 -070079 return r.toString();
Hans Boehm682ff5e2015-03-09 14:40:25 -070080 }
81
82 // Primarily for debugging; clearly not exact
83 public double doubleValue() {
84 return mNum.doubleValue() / mDen.doubleValue();
85 }
86
87 public CR CRValue() {
88 return CR.valueOf(mNum).divide(CR.valueOf(mDen));
89 }
90
91 private boolean tooBig() {
92 if (mDen.equals(BigInteger.ONE)) return false;
93 return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE);
94 }
95
96 // return an equivalent fraction with a positive denominator.
Hans Boehm9e855e82015-04-22 18:03:28 -070097 private BoundedRational positiveDen() {
Hans Boehm682ff5e2015-03-09 14:40:25 -070098 if (mDen.compareTo(BigInteger.ZERO) > 0) return this;
99 return new BoundedRational(mNum.negate(), mDen.negate());
100 }
101
102 // Return an equivalent fraction in lowest terms.
103 private BoundedRational reduce() {
104 if (mDen.equals(BigInteger.ONE)) return this; // Optimization only
105 BigInteger divisor = mNum.gcd(mDen);
106 return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
107 }
108
109 // Return a possibly reduced version of this that's not tooBig.
110 // Return null if none exists.
111 private BoundedRational maybeReduce() {
112 if (!tooBig()) return this;
Hans Boehm9e855e82015-04-22 18:03:28 -0700113 BoundedRational result = positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700114 if (!result.tooBig()) return this;
115 result = result.reduce();
116 if (!result.tooBig()) return this;
117 return null;
118 }
119
120 public int compareTo(BoundedRational r) {
121 // Compare by multiplying both sides by denominators,
122 // invert result if denominator product was negative.
123 return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen))
124 * mDen.signum() * r.mDen.signum();
125 }
126
127 public int signum() {
Hans Boehm75ca21c2015-03-11 18:43:24 -0700128 return mNum.signum() * mDen.signum();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700129 }
130
131 public boolean equals(BoundedRational r) {
132 return compareTo(r) == 0;
133 }
134
135 // We use static methods for arithmetic, so that we can
136 // easily handle the null case.
137 // We try to catch domain errors whenever possible, sometimes even when
138 // one of the arguments is null, but not relevant.
139
140 // Returns equivalent BigInteger result if it exists, null if not.
141 public static BigInteger asBigInteger(BoundedRational r) {
142 if (r == null) return null;
143 if (!r.mDen.equals(BigInteger.ONE)) r = r.reduce();
144 if (!r.mDen.equals(BigInteger.ONE)) return null;
145 return r.mNum;
146 }
147 public static BoundedRational add(BoundedRational r1, BoundedRational r2) {
148 if (r1 == null || r2 == null) return null;
149 final BigInteger den = r1.mDen.multiply(r2.mDen);
150 final BigInteger num = r1.mNum.multiply(r2.mDen)
151 .add(r2.mNum.multiply(r1.mDen));
152 return new BoundedRational(num,den).maybeReduce();
153 }
154
155 public static BoundedRational negate(BoundedRational r) {
156 if (r == null) return null;
157 return new BoundedRational(r.mNum.negate(), r.mDen);
158 }
159
160 static BoundedRational subtract(BoundedRational r1, BoundedRational r2) {
161 return add(r1, negate(r2));
162 }
163
164 static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
165 // It's tempting but marginally unsound to reduce 0 * null to zero.
166 // The null could represent an infinite value, for which we
167 // failed to throw an exception because it was too big.
168 if (r1 == null || r2 == null) return null;
169 final BigInteger num = r1.mNum.multiply(r2.mNum);
170 final BigInteger den = r1.mDen.multiply(r2.mDen);
171 return new BoundedRational(num,den).maybeReduce();
172 }
173
Hans Boehmfbcef702015-04-27 18:07:47 -0700174 public static class ZeroDivisionException extends ArithmeticException {
175 public ZeroDivisionException() {
176 super("Division by zero");
177 }
178 }
179
Hans Boehm682ff5e2015-03-09 14:40:25 -0700180 static BoundedRational inverse(BoundedRational r) {
181 if (r == null) return null;
182 if (r.mNum.equals(BigInteger.ZERO)) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700183 throw new ZeroDivisionException();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700184 }
185 return new BoundedRational(r.mDen, r.mNum);
186 }
187
188 static BoundedRational divide(BoundedRational r1, BoundedRational r2) {
189 return multiply(r1, inverse(r2));
190 }
191
192 static BoundedRational sqrt(BoundedRational r) {
193 // Return non-null if numerator and denominator are small perfect
194 // squares.
195 if (r == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700196 r = r.positiveDen().reduce();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700197 if (r.mNum.compareTo(BigInteger.ZERO) < 0) {
198 throw new ArithmeticException("sqrt(negative)");
199 }
200 final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
201 r.mNum.doubleValue())));
202 if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) return null;
203 final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
204 r.mDen.doubleValue())));
Hans Boehm75ca21c2015-03-11 18:43:24 -0700205 if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) return null;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700206 return new BoundedRational(num_sqrt, den_sqrt);
207 }
208
209 public final static BoundedRational ZERO = new BoundedRational(0);
210 public final static BoundedRational HALF = new BoundedRational(1,2);
211 public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2);
212 public final static BoundedRational ONE = new BoundedRational(1);
213 public final static BoundedRational MINUS_ONE = new BoundedRational(-1);
214 public final static BoundedRational TWO = new BoundedRational(2);
215 public final static BoundedRational MINUS_TWO = new BoundedRational(-2);
216 public final static BoundedRational THIRTY = new BoundedRational(30);
217 public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
218 public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
219 public final static BoundedRational MINUS_FORTY_FIVE =
220 new BoundedRational(-45);
221 public final static BoundedRational NINETY = new BoundedRational(90);
222 public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
223
224 private static BoundedRational map0to0(BoundedRational r) {
225 if (r == null) return null;
226 if (r.mNum.equals(BigInteger.ZERO)) {
227 return ZERO;
228 }
229 return null;
230 }
231
Hans Boehm4db31b42015-05-31 12:19:05 -0700232 private static BoundedRational map0to1(BoundedRational r) {
233 if (r == null) return null;
234 if (r.mNum.equals(BigInteger.ZERO)) {
235 return ONE;
236 }
237 return null;
238 }
239
Hans Boehm682ff5e2015-03-09 14:40:25 -0700240 private static BoundedRational map1to0(BoundedRational r) {
241 if (r == null) return null;
242 if (r.mNum.equals(r.mDen)) {
243 return ZERO;
244 }
245 return null;
246 }
247
248 // Throw an exception if the argument is definitely out of bounds for asin
249 // or acos.
250 private static void checkAsinDomain(BoundedRational r) {
251 if (r == null) return;
252 if (r.mNum.abs().compareTo(r.mDen.abs()) > 0) {
253 throw new ArithmeticException("inverse trig argument out of range");
254 }
255 }
256
257 public static BoundedRational sin(BoundedRational r) {
258 return map0to0(r);
259 }
260
261 private final static BigInteger BIG360 = BigInteger.valueOf(360);
262
263 public static BoundedRational degreeSin(BoundedRational r) {
264 final BigInteger r_BI = asBigInteger(r);
265 if (r_BI == null) return null;
266 final int r_int = r_BI.mod(BIG360).intValue();
267 if (r_int % 30 != 0) return null;
268 switch (r_int / 10) {
269 case 0:
270 return ZERO;
271 case 3: // 30 degrees
272 return HALF;
273 case 9:
274 return ONE;
275 case 15:
276 return HALF;
277 case 18: // 180 degrees
278 return ZERO;
279 case 21:
280 return MINUS_HALF;
281 case 27:
282 return MINUS_ONE;
283 case 33:
284 return MINUS_HALF;
285 default:
286 return null;
287 }
288 }
289
290 public static BoundedRational asin(BoundedRational r) {
291 checkAsinDomain(r);
292 return map0to0(r);
293 }
294
295 public static BoundedRational degreeAsin(BoundedRational r) {
296 checkAsinDomain(r);
297 final BigInteger r2_BI = asBigInteger(multiply(r, TWO));
298 if (r2_BI == null) return null;
299 final int r2_int = r2_BI.intValue();
300 // Somewhat surprisingly, it seems to be the case that the following
301 // covers all rational cases:
302 switch (r2_int) {
303 case -2: // Corresponding to -1 argument
304 return MINUS_NINETY;
305 case -1: // Corresponding to -1/2 argument
306 return MINUS_THIRTY;
307 case 0:
308 return ZERO;
309 case 1:
310 return THIRTY;
311 case 2:
312 return NINETY;
313 default:
314 throw new AssertionError("Impossible asin arg");
315 }
316 }
317
318 public static BoundedRational tan(BoundedRational r) {
319 // Unlike the degree case, we cannot check for the singularity,
320 // since it occurs at an irrational argument.
321 return map0to0(r);
322 }
323
324 public static BoundedRational degreeTan(BoundedRational r) {
325 final BoundedRational degree_sin = degreeSin(r);
326 final BoundedRational degree_cos = degreeCos(r);
327 if (degree_cos != null && degree_cos.mNum.equals(BigInteger.ZERO)) {
328 throw new ArithmeticException("Tangent undefined");
329 }
330 return divide(degree_sin, degree_cos);
331 }
332
333 public static BoundedRational atan(BoundedRational r) {
334 return map0to0(r);
335 }
336
337 public static BoundedRational degreeAtan(BoundedRational r) {
338 final BigInteger r_BI = asBigInteger(r);
339 if (r_BI == null) return null;
340 if (r_BI.abs().compareTo(BigInteger.ONE) > 0) return null;
341 final int r_int = r_BI.intValue();
342 // Again, these seem to be all rational cases:
343 switch (r_int) {
344 case -1:
345 return MINUS_FORTY_FIVE;
346 case 0:
347 return ZERO;
348 case 1:
349 return FORTY_FIVE;
350 default:
351 throw new AssertionError("Impossible atan arg");
352 }
353 }
354
355 public static BoundedRational cos(BoundedRational r) {
Hans Boehm4db31b42015-05-31 12:19:05 -0700356 return map0to1(r);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700357 }
358
359 public static BoundedRational degreeCos(BoundedRational r) {
360 return degreeSin(add(r, NINETY));
361 }
362
363 public static BoundedRational acos(BoundedRational r) {
364 checkAsinDomain(r);
365 return map1to0(r);
366 }
367
368 public static BoundedRational degreeAcos(BoundedRational r) {
369 final BoundedRational asin_r = degreeAsin(r);
370 return subtract(NINETY, asin_r);
371 }
372
373 private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
374
375 // Compute an integral power of this
376 private BoundedRational pow(BigInteger exp) {
377 if (exp.compareTo(BigInteger.ZERO) < 0) {
378 return inverse(pow(exp.negate()));
379 }
380 if (exp.equals(BigInteger.ONE)) return this;
381 if (exp.and(BigInteger.ONE).intValue() == 1) {
382 return multiply(pow(exp.subtract(BigInteger.ONE)), this);
383 }
384 if (exp.equals(BigInteger.ZERO)) {
385 return ONE;
386 }
387 BoundedRational tmp = pow(exp.shiftRight(1));
388 return multiply(tmp, tmp);
389 }
390
391 public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
392 if (exp == null) return null;
393 if (exp.mNum.equals(BigInteger.ZERO)) {
394 return new BoundedRational(1);
395 }
396 if (base == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700397 exp = exp.reduce().positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700398 if (!exp.mDen.equals(BigInteger.ONE)) return null;
399 return base.pow(exp.mNum);
400 }
401
402 public static BoundedRational ln(BoundedRational r) {
Hans Boehm9e855e82015-04-22 18:03:28 -0700403 if (r != null && r.signum() <= 0) {
404 throw new ArithmeticException("log(non-positive)");
Hans Boehm682ff5e2015-03-09 14:40:25 -0700405 }
406 return map1to0(r);
407 }
408
Hans Boehm4db31b42015-05-31 12:19:05 -0700409 public static BoundedRational exp(BoundedRational r) {
410 return map0to1(r);
411 }
412
Hans Boehm682ff5e2015-03-09 14:40:25 -0700413 // Return the base 10 log of n, if n is a power of 10, -1 otherwise.
Hans Boehm9e855e82015-04-22 18:03:28 -0700414 // n must be positive.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700415 private static long b10Log(BigInteger n) {
416 // This algorithm is very naive, but we doubt it matters.
417 long count = 0;
418 while (n.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
419 n = n.divide(BigInteger.TEN);
420 ++count;
421 }
422 if (n.equals(BigInteger.ONE)) {
423 return count;
424 }
425 return -1;
426 }
427
428 public static BoundedRational log(BoundedRational r) {
429 if (r == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700430 if (r.signum() <= 0) {
431 throw new ArithmeticException("log(non-positive)");
Hans Boehm682ff5e2015-03-09 14:40:25 -0700432 }
Hans Boehm9e855e82015-04-22 18:03:28 -0700433 r = r.reduce().positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700434 if (r == null) return null;
435 if (r.mDen.equals(BigInteger.ONE)) {
436 long log = b10Log(r.mNum);
437 if (log != -1) return new BoundedRational(log);
438 } else if (r.mNum.equals(BigInteger.ONE)) {
439 long log = b10Log(r.mDen);
440 if (log != -1) return new BoundedRational(-log);
441 }
442 return null;
443 }
444
445 // Generalized factorial.
446 // Compute n * (n - step) * (n - 2 * step) * ...
447 // This can be used to compute factorial a bit faster, especially
448 // if BigInteger uses sub-quadratic multiplication.
449 private static BigInteger genFactorial(long n, long step) {
450 if (n > 4 * step) {
451 BigInteger prod1 = genFactorial(n, 2 * step);
Hans Boehmc023b732015-04-29 11:30:47 -0700452 if (Thread.interrupted()) {
453 throw new AbortedError();
454 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700455 BigInteger prod2 = genFactorial(n - step, 2 * step);
Hans Boehmc023b732015-04-29 11:30:47 -0700456 if (Thread.interrupted()) {
457 throw new AbortedError();
458 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700459 return prod1.multiply(prod2);
460 } else {
461 BigInteger res = BigInteger.valueOf(n);
462 for (long i = n - step; i > 1; i -= step) {
463 res = res.multiply(BigInteger.valueOf(i));
464 }
465 return res;
466 }
467 }
468
469 // Factorial;
470 // always produces non-null (or exception) when called on non-null r.
471 public static BoundedRational fact(BoundedRational r) {
472 if (r == null) return null; // Caller should probably preclude this case.
473 final BigInteger r_BI = asBigInteger(r);
474 if (r_BI == null) {
475 throw new ArithmeticException("Non-integral factorial argument");
476 }
477 if (r_BI.signum() < 0) {
478 throw new ArithmeticException("Negative factorial argument");
479 }
480 if (r_BI.bitLength() > 30) {
481 // Will fail. LongValue() may not work. Punt now.
482 throw new ArithmeticException("Factorial argument too big");
483 }
484 return new BoundedRational(genFactorial(r_BI.longValue(), 1));
485 }
486
487 private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
Hans Boehmcd740592015-06-13 21:12:23 -0700488 private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700489
490 // Return the number of decimal digits to the right of the
491 // decimal point required to represent the argument exactly,
492 // or Integer.MAX_VALUE if it's not possible.
493 // Never returns a value les than zero, even if r is
494 // a power of ten.
495 static int digitsRequired(BoundedRational r) {
496 if (r == null) return Integer.MAX_VALUE;
497 int powers_of_two = 0; // Max power of 2 that divides denominator
498 int powers_of_five = 0; // Max power of 5 that divides denominator
499 // Try the easy case first to speed things up.
500 if (r.mDen.equals(BigInteger.ONE)) return 0;
501 r = r.reduce();
502 BigInteger den = r.mDen;
503 while (!den.testBit(0)) {
504 ++powers_of_two;
505 den = den.shiftRight(1);
506 }
507 while (den.mod(BIG_FIVE).equals(BigInteger.ZERO)) {
508 ++powers_of_five;
509 den = den.divide(BIG_FIVE);
510 }
511 // If the denominator has a factor of other than 2 or 5
512 // (the divisors of 10), the decimal expansion does not
513 // terminate. Multiplying the fraction by any number of
514 // powers of 10 will not cancel the demoniator.
515 // (Recall the fraction was in lowest terms to start with.)
516 // Otherwise the powers of 10 we need to cancel the denominator
517 // is the larger of powers_of_two and powers_of_five.
Hans Boehmcd740592015-06-13 21:12:23 -0700518 if (!den.equals(BigInteger.ONE) && !den.equals(BIG_MINUS_ONE)) {
519 return Integer.MAX_VALUE;
520 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700521 return Math.max(powers_of_two, powers_of_five);
522 }
523}