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