blob: f7695535b06dc0f7cc30b1e421ffa7c31dc10673 [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
Hans Boehm995e5eb2016-02-08 11:03:01 -080019import java.util.Random;
Hans Boehm682ff5e2015-03-09 14:40:25 -070020
21import java.math.BigInteger;
22import com.hp.creals.CR;
23
Hans Boehmf599db72015-08-10 16:19:24 -070024/**
25 * Rational numbers that may turn to null if they get too big.
26 * For many operations, if the length of the nuumerator plus the length of the denominator exceeds
27 * a maximum size, we simply return null, and rely on our caller do something else.
28 * We currently never return null for a pure integer or for a BoundedRational that has just been
29 * constructed.
30 *
31 * We also implement a number of irrational functions. These return a non-null result only when
32 * the result is known to be rational.
33 */
Hans Boehm682ff5e2015-03-09 14:40:25 -070034public class BoundedRational {
Hans Boehmf599db72015-08-10 16:19:24 -070035 // TODO: Consider returning null for integers. With some care, large factorials might become
36 // much faster.
Hans Boehm682ff5e2015-03-09 14:40:25 -070037 // TODO: Maybe eventually make this extend Number?
Hans Boehmf599db72015-08-10 16:19:24 -070038
Hans Boehm995e5eb2016-02-08 11:03:01 -080039 private static final int MAX_SIZE = 10000; // total, in bits
Hans Boehm682ff5e2015-03-09 14:40:25 -070040
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
Hans Boehmf599db72015-08-10 16:19:24 -070064 /**
65 * Convert to String reflecting raw representation.
66 * Debug or log messages only, not pretty.
67 */
Hans Boehm75ca21c2015-03-11 18:43:24 -070068 public String toString() {
69 return mNum.toString() + "/" + mDen.toString();
70 }
71
Hans Boehmf599db72015-08-10 16:19:24 -070072 /**
73 * Convert to readable String.
Hans Boehm65a99a42016-02-03 18:16:07 -080074 * Intended for output to user. More expensive, less useful for debugging than
Hans Boehmf599db72015-08-10 16:19:24 -070075 * toString(). Not internationalized.
76 */
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070077 public String toNiceString() {
Hans Boehm65a99a42016-02-03 18:16:07 -080078 final BoundedRational nicer = reduce().positiveDen();
Hans Boehm4a6b7cb2015-04-03 18:41:52 -070079 String result = nicer.mNum.toString();
80 if (!nicer.mDen.equals(BigInteger.ONE)) {
81 result += "/" + nicer.mDen;
82 }
83 return result;
84 }
85
Hans Boehm682ff5e2015-03-09 14:40:25 -070086 public static String toString(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -070087 if (r == null) {
88 return "not a small rational";
89 }
Hans Boehm75ca21c2015-03-11 18:43:24 -070090 return r.toString();
Hans Boehm682ff5e2015-03-09 14:40:25 -070091 }
92
Hans Boehmf599db72015-08-10 16:19:24 -070093 /**
Hans Boehm65a99a42016-02-03 18:16:07 -080094 * Return a string with n copies of c.
95 */
Hans Boehm995e5eb2016-02-08 11:03:01 -080096 static String repeat(char c, int n) {
Hans Boehm65a99a42016-02-03 18:16:07 -080097 final StringBuilder result = new StringBuilder();
98 for (int i = 0; i < n; ++i) {
99 result.append(c);
100 }
101 return result.toString();
102 }
103
104 /*
105 * Returns a truncated (rounded towards 0) representation of the result.
106 * Includes n digits to the right of the decimal point.
107 * @param n result precision, >= 0
108 */
Hans Boehm995e5eb2016-02-08 11:03:01 -0800109 public String toStringTruncated(int n) {
Hans Boehm65a99a42016-02-03 18:16:07 -0800110 String digits = mNum.abs().multiply(BigInteger.TEN.pow(n)).divide(mDen.abs()).toString();
111 int len = digits.length();
112 if (len < n + 1) {
113 digits = repeat('0', n + 1 - len) + digits;
114 len = n + 1;
115 }
116 return (signum() < 0 ? "-" : "") + digits.substring(0, len - n) + "."
117 + digits.substring(len - n);
118 }
119
120 /**
Hans Boehmf599db72015-08-10 16:19:24 -0700121 * Return a double approximation.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800122 * The result is correctly rounded if numerator and denominator are
123 * exactly representable as a double.
124 * TODO: This should always be correctly rounded.
Hans Boehmf599db72015-08-10 16:19:24 -0700125 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700126 public double doubleValue() {
127 return mNum.doubleValue() / mDen.doubleValue();
128 }
129
Hans Boehm995e5eb2016-02-08 11:03:01 -0800130 public CR crValue() {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700131 return CR.valueOf(mNum).divide(CR.valueOf(mDen));
132 }
133
Hans Boehm995e5eb2016-02-08 11:03:01 -0800134 public int intValue() {
135 BoundedRational reduced = reduce();
136 if (!reduced.mDen.equals(BigInteger.ONE)) {
137 throw new ArithmeticException("intValue of non-int");
138 }
139 return reduced.mNum.intValue();
140 }
141
Hans Boehm82e5a2f2015-07-20 20:08:14 -0700142 // Approximate number of bits to left of binary point.
Hans Boehm995e5eb2016-02-08 11:03:01 -0800143 // Negative indicates leading zeroes to the right of binary point.
Hans Boehm82e5a2f2015-07-20 20:08:14 -0700144 public int wholeNumberBits() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800145 if (mNum.signum() == 0) {
146 return Integer.MIN_VALUE;
147 } else {
148 return mNum.bitLength() - mDen.bitLength();
149 }
Hans Boehm82e5a2f2015-07-20 20:08:14 -0700150 }
151
Hans Boehm682ff5e2015-03-09 14:40:25 -0700152 private boolean tooBig() {
Hans Boehmf599db72015-08-10 16:19:24 -0700153 if (mDen.equals(BigInteger.ONE)) {
154 return false;
155 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700156 return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE);
157 }
158
Hans Boehmf599db72015-08-10 16:19:24 -0700159 /**
160 * Return an equivalent fraction with a positive denominator.
161 */
Hans Boehm9e855e82015-04-22 18:03:28 -0700162 private BoundedRational positiveDen() {
Hans Boehmf599db72015-08-10 16:19:24 -0700163 if (mDen.signum() > 0) {
164 return this;
165 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700166 return new BoundedRational(mNum.negate(), mDen.negate());
167 }
168
Hans Boehmf599db72015-08-10 16:19:24 -0700169 /**
170 * Return an equivalent fraction in lowest terms.
171 * Denominator sign may remain negative.
172 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700173 private BoundedRational reduce() {
Hans Boehmf599db72015-08-10 16:19:24 -0700174 if (mDen.equals(BigInteger.ONE)) {
175 return this; // Optimization only
176 }
177 final BigInteger divisor = mNum.gcd(mDen);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700178 return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
179 }
180
Hans Boehm995e5eb2016-02-08 11:03:01 -0800181 static Random sReduceRng = new Random();
182
Hans Boehmf599db72015-08-10 16:19:24 -0700183 /**
184 * Return a possibly reduced version of this that's not tooBig().
185 * Return null if none exists.
186 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700187 private BoundedRational maybeReduce() {
Hans Boehm995e5eb2016-02-08 11:03:01 -0800188 // Reduce randomly, with 1/16 probability, or if the result is too big.
189 if (!tooBig() && (sReduceRng.nextInt() & 0xf) != 0) {
Hans Boehmf599db72015-08-10 16:19:24 -0700190 return this;
191 }
Hans Boehm9e855e82015-04-22 18:03:28 -0700192 BoundedRational result = positiveDen();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700193 result = result.reduce();
Hans Boehmf599db72015-08-10 16:19:24 -0700194 if (!result.tooBig()) {
195 return this;
196 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700197 return null;
198 }
199
200 public int compareTo(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700201 // Compare by multiplying both sides by denominators, invert result if denominator product
202 // was negative.
203 return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen)) * mDen.signum()
204 * r.mDen.signum();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700205 }
206
207 public int signum() {
Hans Boehm75ca21c2015-03-11 18:43:24 -0700208 return mNum.signum() * mDen.signum();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700209 }
210
211 public boolean equals(BoundedRational r) {
212 return compareTo(r) == 0;
213 }
214
Hans Boehmf599db72015-08-10 16:19:24 -0700215 // We use static methods for arithmetic, so that we can easily handle the null case. We try
216 // to catch domain errors whenever possible, sometimes even when one of the arguments is null,
217 // but not relevant.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700218
Hans Boehmf599db72015-08-10 16:19:24 -0700219 /**
220 * Returns equivalent BigInteger result if it exists, null if not.
221 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700222 public static BigInteger asBigInteger(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700223 if (r == null) {
224 return null;
225 }
226 final BigInteger[] quotAndRem = r.mNum.divideAndRemainder(r.mDen);
227 if (quotAndRem[1].signum() == 0) {
228 return quotAndRem[0];
229 } else {
230 return null;
231 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700232 }
233 public static BoundedRational add(BoundedRational r1, BoundedRational r2) {
Hans Boehmf599db72015-08-10 16:19:24 -0700234 if (r1 == null || r2 == null) {
235 return null;
236 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700237 final BigInteger den = r1.mDen.multiply(r2.mDen);
Hans Boehmf599db72015-08-10 16:19:24 -0700238 final BigInteger num = r1.mNum.multiply(r2.mDen).add(r2.mNum.multiply(r1.mDen));
Hans Boehm682ff5e2015-03-09 14:40:25 -0700239 return new BoundedRational(num,den).maybeReduce();
240 }
241
Hans Boehm995e5eb2016-02-08 11:03:01 -0800242 /**
243 * Return the argument, but with the opposite sign.
244 * Returns null only for a null argument.
245 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700246 public static BoundedRational negate(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700247 if (r == null) {
248 return null;
249 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700250 return new BoundedRational(r.mNum.negate(), r.mDen);
251 }
252
253 static BoundedRational subtract(BoundedRational r1, BoundedRational r2) {
254 return add(r1, negate(r2));
255 }
256
Hans Boehm995e5eb2016-02-08 11:03:01 -0800257 /**
258 * Return product of r1 and r2 without reducing the result.
259 */
260 private static BoundedRational rawMultiply(BoundedRational r1, BoundedRational r2) {
Hans Boehmf599db72015-08-10 16:19:24 -0700261 // It's tempting but marginally unsound to reduce 0 * null to 0. The null could represent
262 // an infinite value, for which we failed to throw an exception because it was too big.
263 if (r1 == null || r2 == null) {
264 return null;
265 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800266 // Optimize the case of our special ONE constant, since that's cheap and somewhat frequent.
267 if (r1 == ONE) {
268 return r2;
269 }
270 if (r2 == ONE) {
271 return r1;
272 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700273 final BigInteger num = r1.mNum.multiply(r2.mNum);
274 final BigInteger den = r1.mDen.multiply(r2.mDen);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800275 return new BoundedRational(num,den);
276 }
277
278 static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
279 return rawMultiply(r1, r2).maybeReduce();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700280 }
281
Hans Boehmfbcef702015-04-27 18:07:47 -0700282 public static class ZeroDivisionException extends ArithmeticException {
283 public ZeroDivisionException() {
284 super("Division by zero");
285 }
286 }
287
Hans Boehmf599db72015-08-10 16:19:24 -0700288 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800289 * Return the reciprocal of r (or null if the argument was null).
Hans Boehmf599db72015-08-10 16:19:24 -0700290 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700291 static BoundedRational inverse(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700292 if (r == null) {
293 return null;
294 }
295 if (r.mNum.signum() == 0) {
Hans Boehmfbcef702015-04-27 18:07:47 -0700296 throw new ZeroDivisionException();
Hans Boehm682ff5e2015-03-09 14:40:25 -0700297 }
298 return new BoundedRational(r.mDen, r.mNum);
299 }
300
301 static BoundedRational divide(BoundedRational r1, BoundedRational r2) {
302 return multiply(r1, inverse(r2));
303 }
304
305 static BoundedRational sqrt(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700306 // Return non-null if numerator and denominator are small perfect squares.
307 if (r == null) {
308 return null;
309 }
Hans Boehm9e855e82015-04-22 18:03:28 -0700310 r = r.positiveDen().reduce();
Hans Boehmf599db72015-08-10 16:19:24 -0700311 if (r.mNum.signum() < 0) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700312 throw new ArithmeticException("sqrt(negative)");
313 }
Hans Boehmf599db72015-08-10 16:19:24 -0700314 final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mNum.doubleValue())));
315 if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) {
316 return null;
317 }
318 final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mDen.doubleValue())));
319 if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) {
320 return null;
321 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700322 return new BoundedRational(num_sqrt, den_sqrt);
323 }
324
325 public final static BoundedRational ZERO = new BoundedRational(0);
326 public final static BoundedRational HALF = new BoundedRational(1,2);
327 public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800328 public final static BoundedRational THIRD = new BoundedRational(1,3);
329 public final static BoundedRational QUARTER = new BoundedRational(1,4);
330 public final static BoundedRational SIXTH = new BoundedRational(1,6);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700331 public final static BoundedRational ONE = new BoundedRational(1);
332 public final static BoundedRational MINUS_ONE = new BoundedRational(-1);
333 public final static BoundedRational TWO = new BoundedRational(2);
334 public final static BoundedRational MINUS_TWO = new BoundedRational(-2);
Hans Boehm995e5eb2016-02-08 11:03:01 -0800335 public final static BoundedRational TEN = new BoundedRational(10);
336 public final static BoundedRational TWELVE = new BoundedRational(12);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700337 public final static BoundedRational THIRTY = new BoundedRational(30);
338 public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
339 public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
Hans Boehmf599db72015-08-10 16:19:24 -0700340 public final static BoundedRational MINUS_FORTY_FIVE = new BoundedRational(-45);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700341 public final static BoundedRational NINETY = new BoundedRational(90);
342 public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
343
Hans Boehm682ff5e2015-03-09 14:40:25 -0700344 private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
345
Hans Boehmf599db72015-08-10 16:19:24 -0700346 /**
Hans Boehm995e5eb2016-02-08 11:03:01 -0800347 * Compute integral power of this, assuming this has been reduced and exp is >= 0.
348 */
349 private BoundedRational rawPow(BigInteger exp) {
350 if (exp.equals(BigInteger.ONE)) {
351 return this;
352 }
353 if (exp.and(BigInteger.ONE).intValue() == 1) {
354 return rawMultiply(rawPow(exp.subtract(BigInteger.ONE)), this);
355 }
356 if (exp.signum() == 0) {
357 return ONE;
358 }
359 BoundedRational tmp = rawPow(exp.shiftRight(1));
360 if (Thread.interrupted()) {
361 throw new CR.AbortedException();
362 }
363 return rawMultiply(tmp, tmp);
364 }
365
366 /**
Hans Boehmf599db72015-08-10 16:19:24 -0700367 * Compute an integral power of this.
368 */
Hans Boehm995e5eb2016-02-08 11:03:01 -0800369 public BoundedRational pow(BigInteger exp) {
Hans Boehmf599db72015-08-10 16:19:24 -0700370 if (exp.signum() < 0) {
Hans Boehm682ff5e2015-03-09 14:40:25 -0700371 return inverse(pow(exp.negate()));
372 }
Hans Boehmf599db72015-08-10 16:19:24 -0700373 if (exp.equals(BigInteger.ONE)) {
374 return this;
375 }
Hans Boehm995e5eb2016-02-08 11:03:01 -0800376 // Reducing once at the beginning means there's no point in reducing later.
377 return reduce().rawPow(exp);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700378 }
379
380 public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
Hans Boehmf599db72015-08-10 16:19:24 -0700381 if (exp == null) {
382 return null;
383 }
384 if (exp.mNum.signum() == 0) {
385 // Questionable if base has undefined value. Java.lang.Math.pow() returns 1 anyway,
386 // so we do the same.
Hans Boehm682ff5e2015-03-09 14:40:25 -0700387 return new BoundedRational(1);
388 }
Hans Boehmf599db72015-08-10 16:19:24 -0700389 if (base == null) {
390 return null;
391 }
Hans Boehm9e855e82015-04-22 18:03:28 -0700392 exp = exp.reduce().positiveDen();
Hans Boehmf599db72015-08-10 16:19:24 -0700393 if (!exp.mDen.equals(BigInteger.ONE)) {
394 return null;
395 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700396 return base.pow(exp.mNum);
397 }
398
Hans Boehm682ff5e2015-03-09 14:40:25 -0700399
400 private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
Hans Boehmcd740592015-06-13 21:12:23 -0700401 private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700402
Hans Boehmf599db72015-08-10 16:19:24 -0700403 /**
404 * Return the number of decimal digits to the right of the decimal point required to represent
405 * the argument exactly.
406 * Return Integer.MAX_VALUE if that's not possible. Never returns a value less than zero, even
407 * if r is a power of ten.
408 */
Hans Boehm682ff5e2015-03-09 14:40:25 -0700409 static int digitsRequired(BoundedRational r) {
Hans Boehmf599db72015-08-10 16:19:24 -0700410 if (r == null) {
411 return Integer.MAX_VALUE;
412 }
413 int powersOfTwo = 0; // Max power of 2 that divides denominator
414 int powersOfFive = 0; // Max power of 5 that divides denominator
Hans Boehm682ff5e2015-03-09 14:40:25 -0700415 // Try the easy case first to speed things up.
Hans Boehmf599db72015-08-10 16:19:24 -0700416 if (r.mDen.equals(BigInteger.ONE)) {
417 return 0;
418 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700419 r = r.reduce();
420 BigInteger den = r.mDen;
Hans Boehm82e5a2f2015-07-20 20:08:14 -0700421 if (den.bitLength() > MAX_SIZE) {
422 return Integer.MAX_VALUE;
423 }
Hans Boehm682ff5e2015-03-09 14:40:25 -0700424 while (!den.testBit(0)) {
Hans Boehmf599db72015-08-10 16:19:24 -0700425 ++powersOfTwo;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700426 den = den.shiftRight(1);
427 }
Hans Boehmf599db72015-08-10 16:19:24 -0700428 while (den.mod(BIG_FIVE).signum() == 0) {
429 ++powersOfFive;
Hans Boehm682ff5e2015-03-09 14:40:25 -0700430 den = den.divide(BIG_FIVE);
431 }
Hans Boehmf599db72015-08-10 16:19:24 -0700432 // If the denominator has a factor of other than 2 or 5 (the divisors of 10), the decimal
433 // expansion does not terminate. Multiplying the fraction by any number of powers of 10
434 // will not cancel the demoniator. (Recall the fraction was in lowest terms to start
435 // with.) Otherwise the powers of 10 we need to cancel the denominator is the larger of
436 // powersOfTwo and powersOfFive.
Hans Boehmcd740592015-06-13 21:12:23 -0700437 if (!den.equals(BigInteger.ONE) && !den.equals(BIG_MINUS_ONE)) {
438 return Integer.MAX_VALUE;
439 }
Hans Boehmf599db72015-08-10 16:19:24 -0700440 return Math.max(powersOfTwo, powersOfFive);
Hans Boehm682ff5e2015-03-09 14:40:25 -0700441 }
442}