1/*
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
17package com.android.calculator2;
18
19
20import java.math.BigInteger;
21import com.hp.creals.CR;
22
23/**
24 * Rational numbers that may turn to null if they get too big.
25 * For many operations, if the length of the nuumerator plus the length of the denominator exceeds
26 * a maximum size, we simply return null, and rely on our caller do something else.
27 * We currently never return null for a pure integer or for a BoundedRational that has just been
28 * constructed.
29 *
30 * We also implement a number of irrational functions.  These return a non-null result only when
31 * the result is known to be rational.
32 */
33public class BoundedRational {
34    // TODO: Consider returning null for integers.  With some care, large factorials might become
35    // much faster.
36    // TODO: Maybe eventually make this extend Number?
37
38    private static final int MAX_SIZE = 800; // total, in bits
39
40    private final BigInteger mNum;
41    private final BigInteger mDen;
42
43    public BoundedRational(BigInteger n, BigInteger d) {
44        mNum = n;
45        mDen = d;
46    }
47
48    public BoundedRational(BigInteger n) {
49        mNum = n;
50        mDen = BigInteger.ONE;
51    }
52
53    public BoundedRational(long n, long d) {
54        mNum = BigInteger.valueOf(n);
55        mDen = BigInteger.valueOf(d);
56    }
57
58    public BoundedRational(long n) {
59        mNum = BigInteger.valueOf(n);
60        mDen = BigInteger.valueOf(1);
61    }
62
63    /**
64     * Convert to String reflecting raw representation.
65     * Debug or log messages only, not pretty.
66     */
67    public String toString() {
68        return mNum.toString() + "/" + mDen.toString();
69    }
70
71    /**
72     * Convert to readable String.
73     * Intended for output output to user.  More expensive, less useful for debugging than
74     * toString().  Not internationalized.
75     */
76    public String toNiceString() {
77        BoundedRational nicer = reduce().positiveDen();
78        String result = nicer.mNum.toString();
79        if (!nicer.mDen.equals(BigInteger.ONE)) {
80            result += "/" + nicer.mDen;
81        }
82        return result;
83    }
84
85    public static String toString(BoundedRational r) {
86        if (r == null) {
87            return "not a small rational";
88        }
89        return r.toString();
90    }
91
92    /**
93     * Return a double approximation.
94     * Primarily for debugging.
95     */
96    public double doubleValue() {
97        return mNum.doubleValue() / mDen.doubleValue();
98    }
99
100    public CR CRValue() {
101        return CR.valueOf(mNum).divide(CR.valueOf(mDen));
102    }
103
104    // Approximate number of bits to left of binary point.
105    public int wholeNumberBits() {
106        return mNum.bitLength() - mDen.bitLength();
107    }
108
109    private boolean tooBig() {
110        if (mDen.equals(BigInteger.ONE)) {
111            return false;
112        }
113        return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE);
114    }
115
116    /**
117     * Return an equivalent fraction with a positive denominator.
118     */
119    private BoundedRational positiveDen() {
120        if (mDen.signum() > 0) {
121            return this;
122        }
123        return new BoundedRational(mNum.negate(), mDen.negate());
124    }
125
126    /**
127     * Return an equivalent fraction in lowest terms.
128     * Denominator sign may remain negative.
129     */
130    private BoundedRational reduce() {
131        if (mDen.equals(BigInteger.ONE)) {
132            return this;  // Optimization only
133        }
134        final BigInteger divisor = mNum.gcd(mDen);
135        return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
136    }
137
138    /**
139     * Return a possibly reduced version of this that's not tooBig().
140     * Return null if none exists.
141     */
142    private BoundedRational maybeReduce() {
143        if (!tooBig()) {
144            return this;
145        }
146        BoundedRational result = positiveDen();
147        result = result.reduce();
148        if (!result.tooBig()) {
149            return this;
150        }
151        return null;
152    }
153
154    public int compareTo(BoundedRational r) {
155        // Compare by multiplying both sides by denominators, invert result if denominator product
156        // was negative.
157        return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen)) * mDen.signum()
158                * r.mDen.signum();
159    }
160
161    public int signum() {
162        return mNum.signum() * mDen.signum();
163    }
164
165    public boolean equals(BoundedRational r) {
166        return compareTo(r) == 0;
167    }
168
169    // We use static methods for arithmetic, so that we can easily handle the null case.  We try
170    // to catch domain errors whenever possible, sometimes even when one of the arguments is null,
171    // but not relevant.
172
173    /**
174     * Returns equivalent BigInteger result if it exists, null if not.
175     */
176    public static BigInteger asBigInteger(BoundedRational r) {
177        if (r == null) {
178            return null;
179        }
180        final BigInteger[] quotAndRem = r.mNum.divideAndRemainder(r.mDen);
181        if (quotAndRem[1].signum() == 0) {
182            return quotAndRem[0];
183        } else {
184            return null;
185        }
186    }
187    public static BoundedRational add(BoundedRational r1, BoundedRational r2) {
188        if (r1 == null || r2 == null) {
189            return null;
190        }
191        final BigInteger den = r1.mDen.multiply(r2.mDen);
192        final BigInteger num = r1.mNum.multiply(r2.mDen).add(r2.mNum.multiply(r1.mDen));
193        return new BoundedRational(num,den).maybeReduce();
194    }
195
196    public static BoundedRational negate(BoundedRational r) {
197        if (r == null) {
198            return null;
199        }
200        return new BoundedRational(r.mNum.negate(), r.mDen);
201    }
202
203    static BoundedRational subtract(BoundedRational r1, BoundedRational r2) {
204        return add(r1, negate(r2));
205    }
206
207    static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
208        // It's tempting but marginally unsound to reduce 0 * null to 0.  The null could represent
209        // an infinite value, for which we failed to throw an exception because it was too big.
210        if (r1 == null || r2 == null) {
211            return null;
212        }
213        final BigInteger num = r1.mNum.multiply(r2.mNum);
214        final BigInteger den = r1.mDen.multiply(r2.mDen);
215        return new BoundedRational(num,den).maybeReduce();
216    }
217
218    public static class ZeroDivisionException extends ArithmeticException {
219        public ZeroDivisionException() {
220            super("Division by zero");
221        }
222    }
223
224    /**
225     * Return the reciprocal of r (or null).
226     */
227    static BoundedRational inverse(BoundedRational r) {
228        if (r == null) {
229            return null;
230        }
231        if (r.mNum.signum() == 0) {
232            throw new ZeroDivisionException();
233        }
234        return new BoundedRational(r.mDen, r.mNum);
235    }
236
237    static BoundedRational divide(BoundedRational r1, BoundedRational r2) {
238        return multiply(r1, inverse(r2));
239    }
240
241    static BoundedRational sqrt(BoundedRational r) {
242        // Return non-null if numerator and denominator are small perfect squares.
243        if (r == null) {
244            return null;
245        }
246        r = r.positiveDen().reduce();
247        if (r.mNum.signum() < 0) {
248            throw new ArithmeticException("sqrt(negative)");
249        }
250        final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mNum.doubleValue())));
251        if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) {
252            return null;
253        }
254        final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mDen.doubleValue())));
255        if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) {
256            return null;
257        }
258        return new BoundedRational(num_sqrt, den_sqrt);
259    }
260
261    public final static BoundedRational ZERO = new BoundedRational(0);
262    public final static BoundedRational HALF = new BoundedRational(1,2);
263    public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2);
264    public final static BoundedRational ONE = new BoundedRational(1);
265    public final static BoundedRational MINUS_ONE = new BoundedRational(-1);
266    public final static BoundedRational TWO = new BoundedRational(2);
267    public final static BoundedRational MINUS_TWO = new BoundedRational(-2);
268    public final static BoundedRational THIRTY = new BoundedRational(30);
269    public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
270    public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
271    public final static BoundedRational MINUS_FORTY_FIVE = new BoundedRational(-45);
272    public final static BoundedRational NINETY = new BoundedRational(90);
273    public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
274
275    private static BoundedRational map0to0(BoundedRational r) {
276        if (r == null) {
277            return null;
278        }
279        if (r.mNum.signum() == 0) {
280            return ZERO;
281        }
282        return null;
283    }
284
285    private static BoundedRational map0to1(BoundedRational r) {
286        if (r == null) {
287            return null;
288        }
289        if (r.mNum.signum() == 0) {
290            return ONE;
291        }
292        return null;
293    }
294
295    private static BoundedRational map1to0(BoundedRational r) {
296        if (r == null) {
297            return null;
298        }
299        if (r.mNum.equals(r.mDen)) {
300            return ZERO;
301        }
302        return null;
303    }
304
305    // Throw an exception if the argument is definitely out of bounds for asin or acos.
306    private static void checkAsinDomain(BoundedRational r) {
307        if (r == null) {
308            return;
309        }
310        if (r.mNum.abs().compareTo(r.mDen.abs()) > 0) {
311            throw new ArithmeticException("inverse trig argument out of range");
312        }
313    }
314
315    public static BoundedRational sin(BoundedRational r) {
316        return map0to0(r);
317    }
318
319    private final static BigInteger BIG360 = BigInteger.valueOf(360);
320
321    public static BoundedRational degreeSin(BoundedRational r) {
322        final BigInteger r_BI = asBigInteger(r);
323        if (r_BI == null) {
324            return null;
325        }
326        final int r_int = r_BI.mod(BIG360).intValue();
327        if (r_int % 30 != 0) {
328            return null;
329        }
330        switch (r_int / 10) {
331        case 0:
332            return ZERO;
333        case 3: // 30 degrees
334            return HALF;
335        case 9:
336            return ONE;
337        case 15:
338            return HALF;
339        case 18: // 180 degrees
340            return ZERO;
341        case 21:
342            return MINUS_HALF;
343        case 27:
344            return MINUS_ONE;
345        case 33:
346            return MINUS_HALF;
347        default:
348            return null;
349        }
350    }
351
352    public static BoundedRational asin(BoundedRational r) {
353        checkAsinDomain(r);
354        return map0to0(r);
355    }
356
357    public static BoundedRational degreeAsin(BoundedRational r) {
358        checkAsinDomain(r);
359        final BigInteger r2_BI = asBigInteger(multiply(r, TWO));
360        if (r2_BI == null) {
361            return null;
362        }
363        final int r2_int = r2_BI.intValue();
364        // Somewhat surprisingly, it seems to be the case that the following covers all rational
365        // cases:
366        switch (r2_int) {
367        case -2: // Corresponding to -1 argument
368            return MINUS_NINETY;
369        case -1: // Corresponding to -1/2 argument
370            return MINUS_THIRTY;
371        case 0:
372            return ZERO;
373        case 1:
374            return THIRTY;
375        case 2:
376            return NINETY;
377        default:
378            throw new AssertionError("Impossible asin arg");
379        }
380    }
381
382    public static BoundedRational tan(BoundedRational r) {
383        // Unlike the degree case, we cannot check for the singularity, since it occurs at an
384        // irrational argument.
385        return map0to0(r);
386    }
387
388    public static BoundedRational degreeTan(BoundedRational r) {
389        final BoundedRational degSin = degreeSin(r);
390        final BoundedRational degCos = degreeCos(r);
391        if (degCos != null && degCos.mNum.signum() == 0) {
392            throw new ArithmeticException("Tangent undefined");
393        }
394        return divide(degSin, degCos);
395    }
396
397    public static BoundedRational atan(BoundedRational r) {
398        return map0to0(r);
399    }
400
401    public static BoundedRational degreeAtan(BoundedRational r) {
402        final BigInteger r_BI = asBigInteger(r);
403        if (r_BI == null) {
404            return null;
405        }
406        if (r_BI.abs().compareTo(BigInteger.ONE) > 0) {
407            return null;
408        }
409        final int r_int = r_BI.intValue();
410        // Again, these seem to be all rational cases:
411        switch (r_int) {
412        case -1:
413            return MINUS_FORTY_FIVE;
414        case 0:
415            return ZERO;
416        case 1:
417            return FORTY_FIVE;
418        default:
419            throw new AssertionError("Impossible atan arg");
420        }
421    }
422
423    public static BoundedRational cos(BoundedRational r) {
424        return map0to1(r);
425    }
426
427    public static BoundedRational degreeCos(BoundedRational r) {
428        return degreeSin(add(r, NINETY));
429    }
430
431    public static BoundedRational acos(BoundedRational r) {
432        checkAsinDomain(r);
433        return map1to0(r);
434    }
435
436    public static BoundedRational degreeAcos(BoundedRational r) {
437        final BoundedRational asin_r = degreeAsin(r);
438        return subtract(NINETY, asin_r);
439    }
440
441    private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
442
443    /**
444     * Compute an integral power of this.
445     */
446    private BoundedRational pow(BigInteger exp) {
447        if (exp.signum() < 0) {
448            return inverse(pow(exp.negate()));
449        }
450        if (exp.equals(BigInteger.ONE)) {
451            return this;
452        }
453        if (exp.and(BigInteger.ONE).intValue() == 1) {
454            return multiply(pow(exp.subtract(BigInteger.ONE)), this);
455        }
456        if (exp.signum() == 0) {
457            return ONE;
458        }
459        BoundedRational tmp = pow(exp.shiftRight(1));
460        if (Thread.interrupted()) {
461            throw new CR.AbortedException();
462        }
463        return multiply(tmp, tmp);
464    }
465
466    public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
467        if (exp == null) {
468            return null;
469        }
470        if (exp.mNum.signum() == 0) {
471            // Questionable if base has undefined value.  Java.lang.Math.pow() returns 1 anyway,
472            // so we do the same.
473            return new BoundedRational(1);
474        }
475        if (base == null) {
476            return null;
477        }
478        exp = exp.reduce().positiveDen();
479        if (!exp.mDen.equals(BigInteger.ONE)) {
480            return null;
481        }
482        return base.pow(exp.mNum);
483    }
484
485    public static BoundedRational ln(BoundedRational r) {
486        if (r != null && r.signum() <= 0) {
487            throw new ArithmeticException("log(non-positive)");
488        }
489        return map1to0(r);
490    }
491
492    public static BoundedRational exp(BoundedRational r) {
493        return map0to1(r);
494    }
495
496    /**
497     * Return the base 10 log of n, if n is a power of 10, -1 otherwise.
498     * n must be positive.
499     */
500    private static long b10Log(BigInteger n) {
501        // This algorithm is very naive, but we doubt it matters.
502        long count = 0;
503        while (n.mod(BigInteger.TEN).signum() == 0) {
504            if (Thread.interrupted()) {
505                throw new CR.AbortedException();
506            }
507            n = n.divide(BigInteger.TEN);
508            ++count;
509        }
510        if (n.equals(BigInteger.ONE)) {
511            return count;
512        }
513        return -1;
514    }
515
516    public static BoundedRational log(BoundedRational r) {
517        if (r == null) {
518            return null;
519        }
520        if (r.signum() <= 0) {
521            throw new ArithmeticException("log(non-positive)");
522        }
523        r = r.reduce().positiveDen();
524        if (r == null) {
525            return null;
526        }
527        if (r.mDen.equals(BigInteger.ONE)) {
528            long log = b10Log(r.mNum);
529            if (log != -1) {
530                return new BoundedRational(log);
531            }
532        } else if (r.mNum.equals(BigInteger.ONE)) {
533            long log = b10Log(r.mDen);
534            if (log != -1) {
535                return new BoundedRational(-log);
536            }
537        }
538        return null;
539    }
540
541    /**
542     * Generalized factorial.
543     * Compute n * (n - step) * (n - 2 * step) * etc.  This can be used to compute factorial a bit
544     * faster, especially if BigInteger uses sub-quadratic multiplication.
545     */
546    private static BigInteger genFactorial(long n, long step) {
547        if (n > 4 * step) {
548            BigInteger prod1 = genFactorial(n, 2 * step);
549            if (Thread.interrupted()) {
550                throw new CR.AbortedException();
551            }
552            BigInteger prod2 = genFactorial(n - step, 2 * step);
553            if (Thread.interrupted()) {
554                throw new CR.AbortedException();
555            }
556            return prod1.multiply(prod2);
557        } else {
558            if (n == 0) {
559                return BigInteger.ONE;
560            }
561            BigInteger res = BigInteger.valueOf(n);
562            for (long i = n - step; i > 1; i -= step) {
563                res = res.multiply(BigInteger.valueOf(i));
564            }
565            return res;
566        }
567    }
568
569    /**
570     * Factorial function.
571     * Always produces non-null (or exception) when called on non-null r.
572     */
573    public static BoundedRational fact(BoundedRational r) {
574        if (r == null) {
575            return null;
576        }
577        final BigInteger rAsInt = asBigInteger(r);
578        if (rAsInt == null) {
579            throw new ArithmeticException("Non-integral factorial argument");
580        }
581        if (rAsInt.signum() < 0) {
582            throw new ArithmeticException("Negative factorial argument");
583        }
584        if (rAsInt.bitLength() > 30) {
585            // Will fail.  LongValue() may not work. Punt now.
586            throw new ArithmeticException("Factorial argument too big");
587        }
588        return new BoundedRational(genFactorial(rAsInt.longValue(), 1));
589    }
590
591    private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
592    private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1);
593
594    /**
595     * Return the number of decimal digits to the right of the decimal point required to represent
596     * the argument exactly.
597     * Return Integer.MAX_VALUE if that's not possible.  Never returns a value less than zero, even
598     * if r is a power of ten.
599     */
600    static int digitsRequired(BoundedRational r) {
601        if (r == null) {
602            return Integer.MAX_VALUE;
603        }
604        int powersOfTwo = 0;  // Max power of 2 that divides denominator
605        int powersOfFive = 0;  // Max power of 5 that divides denominator
606        // Try the easy case first to speed things up.
607        if (r.mDen.equals(BigInteger.ONE)) {
608            return 0;
609        }
610        r = r.reduce();
611        BigInteger den = r.mDen;
612        if (den.bitLength() > MAX_SIZE) {
613            return Integer.MAX_VALUE;
614        }
615        while (!den.testBit(0)) {
616            ++powersOfTwo;
617            den = den.shiftRight(1);
618        }
619        while (den.mod(BIG_FIVE).signum() == 0) {
620            ++powersOfFive;
621            den = den.divide(BIG_FIVE);
622        }
623        // If the denominator has a factor of other than 2 or 5 (the divisors of 10), the decimal
624        // expansion does not terminate.  Multiplying the fraction by any number of powers of 10
625        // will not cancel the demoniator.  (Recall the fraction was in lowest terms to start
626        // with.) Otherwise the powers of 10 we need to cancel the denominator is the larger of
627        // powersOfTwo and powersOfFive.
628        if (!den.equals(BigInteger.ONE) && !den.equals(BIG_MINUS_ONE)) {
629            return Integer.MAX_VALUE;
630        }
631        return Math.max(powersOfTwo, powersOfFive);
632    }
633}
634