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