blob: 04cf86c5c3a8cb25ec3ca943d85868bb7f6c5863 [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;
32
33public class BoundedRational {
34 // TODO: Maybe eventually make this extend Number?
35 private static final int MAX_SIZE = 400; // total, in bits
36
37 private final BigInteger mNum;
38 private final BigInteger mDen;
39
40 public BoundedRational(BigInteger n, BigInteger d) {
41 mNum = n;
42 mDen = d;
43 }
44
45 public BoundedRational(BigInteger n) {
46 mNum = n;
47 mDen = BigInteger.ONE;
48 }
49
50 public BoundedRational(long n, long d) {
51 mNum = BigInteger.valueOf(n);
52 mDen = BigInteger.valueOf(d);
53 }
54
55 public BoundedRational(long n) {
56 mNum = BigInteger.valueOf(n);
57 mDen = BigInteger.valueOf(1);
58 }
59
60 // Debug or log messages only, not pretty.
Hans Boehm75ca21c2015-03-11 18:43:24 -070061 public String toString() {
62 return mNum.toString() + "/" + mDen.toString();
63 }
64
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070065 // Output to user, more expensive, less useful for debugging
Hans Boehm013969e2015-04-13 20:29:47 -070066 // Not internationalized.
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070067 public String toNiceString() {
Hans Boehm9e855e82015-04-22 18:03:28 -070068 BoundedRational nicer = reduce().positiveDen();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070069 String result = nicer.mNum.toString();
70 if (!nicer.mDen.equals(BigInteger.ONE)) {
71 result += "/" + nicer.mDen;
72 }
73 return result;
74 }
75
Hans Boehm682ff5e2015-03-09 14:40:25 -070076 public static String toString(BoundedRational r) {
77 if (r == null) return "not a small rational";
Hans Boehm75ca21c2015-03-11 18:43:24 -070078 return r.toString();
Hans Boehm682ff5e2015-03-09 14:40:25 -070079 }
80
81 // Primarily for debugging; clearly not exact
82 public double doubleValue() {
83 return mNum.doubleValue() / mDen.doubleValue();
84 }
85
86 public CR CRValue() {
87 return CR.valueOf(mNum).divide(CR.valueOf(mDen));
88 }
89
90 private boolean tooBig() {
91 if (mDen.equals(BigInteger.ONE)) return false;
92 return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE);
93 }
94
95 // return an equivalent fraction with a positive denominator.
Hans Boehm9e855e82015-04-22 18:03:28 -070096 private BoundedRational positiveDen() {
Hans Boehm682ff5e2015-03-09 14:40:25 -070097 if (mDen.compareTo(BigInteger.ZERO) > 0) return this;
98 return new BoundedRational(mNum.negate(), mDen.negate());
99 }
100
101 // Return an equivalent fraction in lowest terms.
102 private BoundedRational reduce() {
103 if (mDen.equals(BigInteger.ONE)) return this; // Optimization only
104 BigInteger divisor = mNum.gcd(mDen);
105 return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
106 }
107
108 // Return a possibly reduced version of this that's not tooBig.
109 // Return null if none exists.
110 private BoundedRational maybeReduce() {
111 if (!tooBig()) return this;
Hans Boehm9e855e82015-04-22 18:03:28 -0700112 BoundedRational result = positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700113 if (!result.tooBig()) return this;
114 result = result.reduce();
115 if (!result.tooBig()) return this;
116 return null;
117 }
118
119 public int compareTo(BoundedRational r) {
120 // Compare by multiplying both sides by denominators,
121 // invert result if denominator product was negative.
122 return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen))
123 * mDen.signum() * r.mDen.signum();
124 }
125
126 public int signum() {
Hans Boehm75ca21c2015-03-11 18:43:24 -0700127 return mNum.signum() * mDen.signum();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700128 }
129
130 public boolean equals(BoundedRational r) {
131 return compareTo(r) == 0;
132 }
133
134 // We use static methods for arithmetic, so that we can
135 // easily handle the null case.
136 // We try to catch domain errors whenever possible, sometimes even when
137 // one of the arguments is null, but not relevant.
138
139 // Returns equivalent BigInteger result if it exists, null if not.
140 public static BigInteger asBigInteger(BoundedRational r) {
141 if (r == null) return null;
142 if (!r.mDen.equals(BigInteger.ONE)) r = r.reduce();
143 if (!r.mDen.equals(BigInteger.ONE)) return null;
144 return r.mNum;
145 }
146 public static BoundedRational add(BoundedRational r1, BoundedRational r2) {
147 if (r1 == null || r2 == null) return null;
148 final BigInteger den = r1.mDen.multiply(r2.mDen);
149 final BigInteger num = r1.mNum.multiply(r2.mDen)
150 .add(r2.mNum.multiply(r1.mDen));
151 return new BoundedRational(num,den).maybeReduce();
152 }
153
154 public static BoundedRational negate(BoundedRational r) {
155 if (r == null) return null;
156 return new BoundedRational(r.mNum.negate(), r.mDen);
157 }
158
159 static BoundedRational subtract(BoundedRational r1, BoundedRational r2) {
160 return add(r1, negate(r2));
161 }
162
163 static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
164 // It's tempting but marginally unsound to reduce 0 * null to zero.
165 // The null could represent an infinite value, for which we
166 // failed to throw an exception because it was too big.
167 if (r1 == null || r2 == null) return null;
168 final BigInteger num = r1.mNum.multiply(r2.mNum);
169 final BigInteger den = r1.mDen.multiply(r2.mDen);
170 return new BoundedRational(num,den).maybeReduce();
171 }
172
173 static BoundedRational inverse(BoundedRational r) {
174 if (r == null) return null;
175 if (r.mNum.equals(BigInteger.ZERO)) {
176 throw new ArithmeticException("Divide by Zero");
177 }
178 return new BoundedRational(r.mDen, r.mNum);
179 }
180
181 static BoundedRational divide(BoundedRational r1, BoundedRational r2) {
182 return multiply(r1, inverse(r2));
183 }
184
185 static BoundedRational sqrt(BoundedRational r) {
186 // Return non-null if numerator and denominator are small perfect
187 // squares.
188 if (r == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700189 r = r.positiveDen().reduce();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700190 if (r.mNum.compareTo(BigInteger.ZERO) < 0) {
191 throw new ArithmeticException("sqrt(negative)");
192 }
193 final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
194 r.mNum.doubleValue())));
195 if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) return null;
196 final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(
197 r.mDen.doubleValue())));
Hans Boehm75ca21c2015-03-11 18:43:24 -0700198 if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) return null;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700199 return new BoundedRational(num_sqrt, den_sqrt);
200 }
201
202 public final static BoundedRational ZERO = new BoundedRational(0);
203 public final static BoundedRational HALF = new BoundedRational(1,2);
204 public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2);
205 public final static BoundedRational ONE = new BoundedRational(1);
206 public final static BoundedRational MINUS_ONE = new BoundedRational(-1);
207 public final static BoundedRational TWO = new BoundedRational(2);
208 public final static BoundedRational MINUS_TWO = new BoundedRational(-2);
209 public final static BoundedRational THIRTY = new BoundedRational(30);
210 public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
211 public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
212 public final static BoundedRational MINUS_FORTY_FIVE =
213 new BoundedRational(-45);
214 public final static BoundedRational NINETY = new BoundedRational(90);
215 public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
216
217 private static BoundedRational map0to0(BoundedRational r) {
218 if (r == null) return null;
219 if (r.mNum.equals(BigInteger.ZERO)) {
220 return ZERO;
221 }
222 return null;
223 }
224
225 private static BoundedRational map1to0(BoundedRational r) {
226 if (r == null) return null;
227 if (r.mNum.equals(r.mDen)) {
228 return ZERO;
229 }
230 return null;
231 }
232
233 // Throw an exception if the argument is definitely out of bounds for asin
234 // or acos.
235 private static void checkAsinDomain(BoundedRational r) {
236 if (r == null) return;
237 if (r.mNum.abs().compareTo(r.mDen.abs()) > 0) {
238 throw new ArithmeticException("inverse trig argument out of range");
239 }
240 }
241
242 public static BoundedRational sin(BoundedRational r) {
243 return map0to0(r);
244 }
245
246 private final static BigInteger BIG360 = BigInteger.valueOf(360);
247
248 public static BoundedRational degreeSin(BoundedRational r) {
249 final BigInteger r_BI = asBigInteger(r);
250 if (r_BI == null) return null;
251 final int r_int = r_BI.mod(BIG360).intValue();
252 if (r_int % 30 != 0) return null;
253 switch (r_int / 10) {
254 case 0:
255 return ZERO;
256 case 3: // 30 degrees
257 return HALF;
258 case 9:
259 return ONE;
260 case 15:
261 return HALF;
262 case 18: // 180 degrees
263 return ZERO;
264 case 21:
265 return MINUS_HALF;
266 case 27:
267 return MINUS_ONE;
268 case 33:
269 return MINUS_HALF;
270 default:
271 return null;
272 }
273 }
274
275 public static BoundedRational asin(BoundedRational r) {
276 checkAsinDomain(r);
277 return map0to0(r);
278 }
279
280 public static BoundedRational degreeAsin(BoundedRational r) {
281 checkAsinDomain(r);
282 final BigInteger r2_BI = asBigInteger(multiply(r, TWO));
283 if (r2_BI == null) return null;
284 final int r2_int = r2_BI.intValue();
285 // Somewhat surprisingly, it seems to be the case that the following
286 // covers all rational cases:
287 switch (r2_int) {
288 case -2: // Corresponding to -1 argument
289 return MINUS_NINETY;
290 case -1: // Corresponding to -1/2 argument
291 return MINUS_THIRTY;
292 case 0:
293 return ZERO;
294 case 1:
295 return THIRTY;
296 case 2:
297 return NINETY;
298 default:
299 throw new AssertionError("Impossible asin arg");
300 }
301 }
302
303 public static BoundedRational tan(BoundedRational r) {
304 // Unlike the degree case, we cannot check for the singularity,
305 // since it occurs at an irrational argument.
306 return map0to0(r);
307 }
308
309 public static BoundedRational degreeTan(BoundedRational r) {
310 final BoundedRational degree_sin = degreeSin(r);
311 final BoundedRational degree_cos = degreeCos(r);
312 if (degree_cos != null && degree_cos.mNum.equals(BigInteger.ZERO)) {
313 throw new ArithmeticException("Tangent undefined");
314 }
315 return divide(degree_sin, degree_cos);
316 }
317
318 public static BoundedRational atan(BoundedRational r) {
319 return map0to0(r);
320 }
321
322 public static BoundedRational degreeAtan(BoundedRational r) {
323 final BigInteger r_BI = asBigInteger(r);
324 if (r_BI == null) return null;
325 if (r_BI.abs().compareTo(BigInteger.ONE) > 0) return null;
326 final int r_int = r_BI.intValue();
327 // Again, these seem to be all rational cases:
328 switch (r_int) {
329 case -1:
330 return MINUS_FORTY_FIVE;
331 case 0:
332 return ZERO;
333 case 1:
334 return FORTY_FIVE;
335 default:
336 throw new AssertionError("Impossible atan arg");
337 }
338 }
339
340 public static BoundedRational cos(BoundedRational r) {
341 // Maps 0 to 1, null otherwise
342 if (r == null) return null;
343 if (r.mNum.equals(BigInteger.ZERO)) {
344 return ONE;
345 }
346 return null;
347 }
348
349 public static BoundedRational degreeCos(BoundedRational r) {
350 return degreeSin(add(r, NINETY));
351 }
352
353 public static BoundedRational acos(BoundedRational r) {
354 checkAsinDomain(r);
355 return map1to0(r);
356 }
357
358 public static BoundedRational degreeAcos(BoundedRational r) {
359 final BoundedRational asin_r = degreeAsin(r);
360 return subtract(NINETY, asin_r);
361 }
362
363 private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
364
365 // Compute an integral power of this
366 private BoundedRational pow(BigInteger exp) {
367 if (exp.compareTo(BigInteger.ZERO) < 0) {
368 return inverse(pow(exp.negate()));
369 }
370 if (exp.equals(BigInteger.ONE)) return this;
371 if (exp.and(BigInteger.ONE).intValue() == 1) {
372 return multiply(pow(exp.subtract(BigInteger.ONE)), this);
373 }
374 if (exp.equals(BigInteger.ZERO)) {
375 return ONE;
376 }
377 BoundedRational tmp = pow(exp.shiftRight(1));
378 return multiply(tmp, tmp);
379 }
380
381 public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
382 if (exp == null) return null;
383 if (exp.mNum.equals(BigInteger.ZERO)) {
384 return new BoundedRational(1);
385 }
386 if (base == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700387 exp = exp.reduce().positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700388 if (!exp.mDen.equals(BigInteger.ONE)) return null;
389 return base.pow(exp.mNum);
390 }
391
392 public static BoundedRational ln(BoundedRational r) {
Hans Boehm9e855e82015-04-22 18:03:28 -0700393 if (r != null && r.signum() <= 0) {
394 throw new ArithmeticException("log(non-positive)");
Hans Boehm682ff5e2015-03-09 14:40:25 -0700395 }
396 return map1to0(r);
397 }
398
399 // Return the base 10 log of n, if n is a power of 10, -1 otherwise.
Hans Boehm9e855e82015-04-22 18:03:28 -0700400 // n must be positive.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700401 private static long b10Log(BigInteger n) {
402 // This algorithm is very naive, but we doubt it matters.
403 long count = 0;
404 while (n.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
405 n = n.divide(BigInteger.TEN);
406 ++count;
407 }
408 if (n.equals(BigInteger.ONE)) {
409 return count;
410 }
411 return -1;
412 }
413
414 public static BoundedRational log(BoundedRational r) {
415 if (r == null) return null;
Hans Boehm9e855e82015-04-22 18:03:28 -0700416 if (r.signum() <= 0) {
417 throw new ArithmeticException("log(non-positive)");
Hans Boehm682ff5e2015-03-09 14:40:25 -0700418 }
Hans Boehm9e855e82015-04-22 18:03:28 -0700419 r = r.reduce().positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700420 if (r == null) return null;
421 if (r.mDen.equals(BigInteger.ONE)) {
422 long log = b10Log(r.mNum);
423 if (log != -1) return new BoundedRational(log);
424 } else if (r.mNum.equals(BigInteger.ONE)) {
425 long log = b10Log(r.mDen);
426 if (log != -1) return new BoundedRational(-log);
427 }
428 return null;
429 }
430
431 // Generalized factorial.
432 // Compute n * (n - step) * (n - 2 * step) * ...
433 // This can be used to compute factorial a bit faster, especially
434 // if BigInteger uses sub-quadratic multiplication.
435 private static BigInteger genFactorial(long n, long step) {
436 if (n > 4 * step) {
437 BigInteger prod1 = genFactorial(n, 2 * step);
438 BigInteger prod2 = genFactorial(n - step, 2 * step);
439 return prod1.multiply(prod2);
440 } else {
441 BigInteger res = BigInteger.valueOf(n);
442 for (long i = n - step; i > 1; i -= step) {
443 res = res.multiply(BigInteger.valueOf(i));
444 }
445 return res;
446 }
447 }
448
449 // Factorial;
450 // always produces non-null (or exception) when called on non-null r.
451 public static BoundedRational fact(BoundedRational r) {
452 if (r == null) return null; // Caller should probably preclude this case.
453 final BigInteger r_BI = asBigInteger(r);
454 if (r_BI == null) {
455 throw new ArithmeticException("Non-integral factorial argument");
456 }
457 if (r_BI.signum() < 0) {
458 throw new ArithmeticException("Negative factorial argument");
459 }
460 if (r_BI.bitLength() > 30) {
461 // Will fail. LongValue() may not work. Punt now.
462 throw new ArithmeticException("Factorial argument too big");
463 }
464 return new BoundedRational(genFactorial(r_BI.longValue(), 1));
465 }
466
467 private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
468
469 // Return the number of decimal digits to the right of the
470 // decimal point required to represent the argument exactly,
471 // or Integer.MAX_VALUE if it's not possible.
472 // Never returns a value les than zero, even if r is
473 // a power of ten.
474 static int digitsRequired(BoundedRational r) {
475 if (r == null) return Integer.MAX_VALUE;
476 int powers_of_two = 0; // Max power of 2 that divides denominator
477 int powers_of_five = 0; // Max power of 5 that divides denominator
478 // Try the easy case first to speed things up.
479 if (r.mDen.equals(BigInteger.ONE)) return 0;
480 r = r.reduce();
481 BigInteger den = r.mDen;
482 while (!den.testBit(0)) {
483 ++powers_of_two;
484 den = den.shiftRight(1);
485 }
486 while (den.mod(BIG_FIVE).equals(BigInteger.ZERO)) {
487 ++powers_of_five;
488 den = den.divide(BIG_FIVE);
489 }
490 // If the denominator has a factor of other than 2 or 5
491 // (the divisors of 10), the decimal expansion does not
492 // terminate. Multiplying the fraction by any number of
493 // powers of 10 will not cancel the demoniator.
494 // (Recall the fraction was in lowest terms to start with.)
495 // Otherwise the powers of 10 we need to cancel the denominator
496 // is the larger of powers_of_two and powers_of_five.
497 if (!den.equals(BigInteger.ONE)) return Integer.MAX_VALUE;
498 return Math.max(powers_of_two, powers_of_five);
499 }
500}