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 import com.hp.creals.CR; 20 21 import java.math.BigInteger; 22 import java.util.Random; 23 24 /** 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 */ 34 public class BoundedRational { 35 // TODO: Consider returning null for integers. With some care, large factorials might become 36 // much faster. 37 // TODO: Maybe eventually make this extend Number? 38 39 private static final int MAX_SIZE = 10000; // 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 /** 65 * Convert to String reflecting raw representation. 66 * Debug or log messages only, not pretty. 67 */ 68 public String toString() { 69 return mNum.toString() + "/" + mDen.toString(); 70 } 71 72 /** 73 * Convert to readable String. 74 * Intended for output to user. More expensive, less useful for debugging than 75 * toString(). Not internationalized. 76 */ 77 public String toNiceString() { 78 final BoundedRational nicer = reduce().positiveDen(); 79 String result = nicer.mNum.toString(); 80 if (!nicer.mDen.equals(BigInteger.ONE)) { 81 result += "/" + nicer.mDen; 82 } 83 return result; 84 } 85 86 public static String toString(BoundedRational r) { 87 if (r == null) { 88 return "not a small rational"; 89 } 90 return r.toString(); 91 } 92 93 /** 94 * Returns a truncated (rounded towards 0) representation of the result. 95 * Includes n digits to the right of the decimal point. 96 * @param n result precision, >= 0 97 */ 98 public String toStringTruncated(int n) { 99 String digits = mNum.abs().multiply(BigInteger.TEN.pow(n)).divide(mDen.abs()).toString(); 100 int len = digits.length(); 101 if (len < n + 1) { 102 digits = StringUtils.repeat('0', n + 1 - len) + digits; 103 len = n + 1; 104 } 105 return (signum() < 0 ? "-" : "") + digits.substring(0, len - n) + "." 106 + digits.substring(len - n); 107 } 108 109 /** 110 * Return a double approximation. 111 * The result is correctly rounded if numerator and denominator are 112 * exactly representable as a double. 113 * TODO: This should always be correctly rounded. 114 */ 115 public double doubleValue() { 116 return mNum.doubleValue() / mDen.doubleValue(); 117 } 118 119 public CR crValue() { 120 return CR.valueOf(mNum).divide(CR.valueOf(mDen)); 121 } 122 123 public int intValue() { 124 BoundedRational reduced = reduce(); 125 if (!reduced.mDen.equals(BigInteger.ONE)) { 126 throw new ArithmeticException("intValue of non-int"); 127 } 128 return reduced.mNum.intValue(); 129 } 130 131 // Approximate number of bits to left of binary point. 132 // Negative indicates leading zeroes to the right of binary point. 133 public int wholeNumberBits() { 134 if (mNum.signum() == 0) { 135 return Integer.MIN_VALUE; 136 } else { 137 return mNum.bitLength() - mDen.bitLength(); 138 } 139 } 140 141 private boolean tooBig() { 142 if (mDen.equals(BigInteger.ONE)) { 143 return false; 144 } 145 return (mNum.bitLength() + mDen.bitLength() > MAX_SIZE); 146 } 147 148 /** 149 * Return an equivalent fraction with a positive denominator. 150 */ 151 private BoundedRational positiveDen() { 152 if (mDen.signum() > 0) { 153 return this; 154 } 155 return new BoundedRational(mNum.negate(), mDen.negate()); 156 } 157 158 /** 159 * Return an equivalent fraction in lowest terms. 160 * Denominator sign may remain negative. 161 */ 162 private BoundedRational reduce() { 163 if (mDen.equals(BigInteger.ONE)) { 164 return this; // Optimization only 165 } 166 final BigInteger divisor = mNum.gcd(mDen); 167 return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor)); 168 } 169 170 static Random sReduceRng = new Random(); 171 172 /** 173 * Return a possibly reduced version of r that's not tooBig(). 174 * Return null if none exists. 175 */ 176 private static BoundedRational maybeReduce(BoundedRational r) { 177 if (r == null) return null; 178 // Reduce randomly, with 1/16 probability, or if the result is too big. 179 if (!r.tooBig() && (sReduceRng.nextInt() & 0xf) != 0) { 180 return r; 181 } 182 BoundedRational result = r.positiveDen(); 183 result = result.reduce(); 184 if (!result.tooBig()) { 185 return result; 186 } 187 return null; 188 } 189 190 public int compareTo(BoundedRational r) { 191 // Compare by multiplying both sides by denominators, invert result if denominator product 192 // was negative. 193 return mNum.multiply(r.mDen).compareTo(r.mNum.multiply(mDen)) * mDen.signum() 194 * r.mDen.signum(); 195 } 196 197 public int signum() { 198 return mNum.signum() * mDen.signum(); 199 } 200 201 public boolean equals(BoundedRational r) { 202 return compareTo(r) == 0; 203 } 204 205 // We use static methods for arithmetic, so that we can easily handle the null case. We try 206 // to catch domain errors whenever possible, sometimes even when one of the arguments is null, 207 // but not relevant. 208 209 /** 210 * Returns equivalent BigInteger result if it exists, null if not. 211 */ 212 public static BigInteger asBigInteger(BoundedRational r) { 213 if (r == null) { 214 return null; 215 } 216 final BigInteger[] quotAndRem = r.mNum.divideAndRemainder(r.mDen); 217 if (quotAndRem[1].signum() == 0) { 218 return quotAndRem[0]; 219 } else { 220 return null; 221 } 222 } 223 public static BoundedRational add(BoundedRational r1, BoundedRational r2) { 224 if (r1 == null || r2 == null) { 225 return null; 226 } 227 final BigInteger den = r1.mDen.multiply(r2.mDen); 228 final BigInteger num = r1.mNum.multiply(r2.mDen).add(r2.mNum.multiply(r1.mDen)); 229 return maybeReduce(new BoundedRational(num,den)); 230 } 231 232 /** 233 * Return the argument, but with the opposite sign. 234 * Returns null only for a null argument. 235 */ 236 public static BoundedRational negate(BoundedRational r) { 237 if (r == null) { 238 return null; 239 } 240 return new BoundedRational(r.mNum.negate(), r.mDen); 241 } 242 243 public static BoundedRational subtract(BoundedRational r1, BoundedRational r2) { 244 return add(r1, negate(r2)); 245 } 246 247 /** 248 * Return product of r1 and r2 without reducing the result. 249 */ 250 private static BoundedRational rawMultiply(BoundedRational r1, BoundedRational r2) { 251 // It's tempting but marginally unsound to reduce 0 * null to 0. The null could represent 252 // an infinite value, for which we failed to throw an exception because it was too big. 253 if (r1 == null || r2 == null) { 254 return null; 255 } 256 // Optimize the case of our special ONE constant, since that's cheap and somewhat frequent. 257 if (r1 == ONE) { 258 return r2; 259 } 260 if (r2 == ONE) { 261 return r1; 262 } 263 final BigInteger num = r1.mNum.multiply(r2.mNum); 264 final BigInteger den = r1.mDen.multiply(r2.mDen); 265 return new BoundedRational(num,den); 266 } 267 268 public static BoundedRational multiply(BoundedRational r1, BoundedRational r2) { 269 return maybeReduce(rawMultiply(r1, r2)); 270 } 271 272 public static class ZeroDivisionException extends ArithmeticException { 273 public ZeroDivisionException() { 274 super("Division by zero"); 275 } 276 } 277 278 /** 279 * Return the reciprocal of r (or null if the argument was null). 280 */ 281 public static BoundedRational inverse(BoundedRational r) { 282 if (r == null) { 283 return null; 284 } 285 if (r.mNum.signum() == 0) { 286 throw new ZeroDivisionException(); 287 } 288 return new BoundedRational(r.mDen, r.mNum); 289 } 290 291 public static BoundedRational divide(BoundedRational r1, BoundedRational r2) { 292 return multiply(r1, inverse(r2)); 293 } 294 295 public static BoundedRational sqrt(BoundedRational r) { 296 // Return non-null if numerator and denominator are small perfect squares. 297 if (r == null) { 298 return null; 299 } 300 r = r.positiveDen().reduce(); 301 if (r.mNum.signum() < 0) { 302 throw new ArithmeticException("sqrt(negative)"); 303 } 304 final BigInteger num_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mNum.doubleValue()))); 305 if (!num_sqrt.multiply(num_sqrt).equals(r.mNum)) { 306 return null; 307 } 308 final BigInteger den_sqrt = BigInteger.valueOf(Math.round(Math.sqrt(r.mDen.doubleValue()))); 309 if (!den_sqrt.multiply(den_sqrt).equals(r.mDen)) { 310 return null; 311 } 312 return new BoundedRational(num_sqrt, den_sqrt); 313 } 314 315 public final static BoundedRational ZERO = new BoundedRational(0); 316 public final static BoundedRational HALF = new BoundedRational(1,2); 317 public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2); 318 public final static BoundedRational THIRD = new BoundedRational(1,3); 319 public final static BoundedRational QUARTER = new BoundedRational(1,4); 320 public final static BoundedRational SIXTH = new BoundedRational(1,6); 321 public final static BoundedRational ONE = new BoundedRational(1); 322 public final static BoundedRational MINUS_ONE = new BoundedRational(-1); 323 public final static BoundedRational TWO = new BoundedRational(2); 324 public final static BoundedRational MINUS_TWO = new BoundedRational(-2); 325 public final static BoundedRational TEN = new BoundedRational(10); 326 public final static BoundedRational TWELVE = new BoundedRational(12); 327 public final static BoundedRational THIRTY = new BoundedRational(30); 328 public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30); 329 public final static BoundedRational FORTY_FIVE = new BoundedRational(45); 330 public final static BoundedRational MINUS_FORTY_FIVE = new BoundedRational(-45); 331 public final static BoundedRational NINETY = new BoundedRational(90); 332 public final static BoundedRational MINUS_NINETY = new BoundedRational(-90); 333 334 private static final BigInteger BIG_TWO = BigInteger.valueOf(2); 335 336 /** 337 * Compute integral power of this, assuming this has been reduced and exp is >= 0. 338 */ 339 private BoundedRational rawPow(BigInteger exp) { 340 if (exp.equals(BigInteger.ONE)) { 341 return this; 342 } 343 if (exp.and(BigInteger.ONE).intValue() == 1) { 344 return rawMultiply(rawPow(exp.subtract(BigInteger.ONE)), this); 345 } 346 if (exp.signum() == 0) { 347 return ONE; 348 } 349 BoundedRational tmp = rawPow(exp.shiftRight(1)); 350 if (Thread.interrupted()) { 351 throw new CR.AbortedException(); 352 } 353 return rawMultiply(tmp, tmp); 354 } 355 356 /** 357 * Compute an integral power of this. 358 */ 359 public BoundedRational pow(BigInteger exp) { 360 if (exp.signum() < 0) { 361 return inverse(pow(exp.negate())); 362 } 363 if (exp.equals(BigInteger.ONE)) { 364 return this; 365 } 366 // Reducing once at the beginning means there's no point in reducing later. 367 return reduce().rawPow(exp); 368 } 369 370 public static BoundedRational pow(BoundedRational base, BoundedRational exp) { 371 if (exp == null) { 372 return null; 373 } 374 if (exp.mNum.signum() == 0) { 375 // Questionable if base has undefined value. Java.lang.Math.pow() returns 1 anyway, 376 // so we do the same. 377 return new BoundedRational(1); 378 } 379 if (base == null) { 380 return null; 381 } 382 exp = exp.reduce().positiveDen(); 383 if (!exp.mDen.equals(BigInteger.ONE)) { 384 return null; 385 } 386 return base.pow(exp.mNum); 387 } 388 389 390 private static final BigInteger BIG_FIVE = BigInteger.valueOf(5); 391 private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1); 392 393 /** 394 * Return the number of decimal digits to the right of the decimal point required to represent 395 * the argument exactly. 396 * Return Integer.MAX_VALUE if that's not possible. Never returns a value less than zero, even 397 * if r is a power of ten. 398 */ 399 public static int digitsRequired(BoundedRational r) { 400 if (r == null) { 401 return Integer.MAX_VALUE; 402 } 403 int powersOfTwo = 0; // Max power of 2 that divides denominator 404 int powersOfFive = 0; // Max power of 5 that divides denominator 405 // Try the easy case first to speed things up. 406 if (r.mDen.equals(BigInteger.ONE)) { 407 return 0; 408 } 409 r = r.reduce(); 410 BigInteger den = r.mDen; 411 if (den.bitLength() > MAX_SIZE) { 412 return Integer.MAX_VALUE; 413 } 414 while (!den.testBit(0)) { 415 ++powersOfTwo; 416 den = den.shiftRight(1); 417 } 418 while (den.mod(BIG_FIVE).signum() == 0) { 419 ++powersOfFive; 420 den = den.divide(BIG_FIVE); 421 } 422 // If the denominator has a factor of other than 2 or 5 (the divisors of 10), the decimal 423 // expansion does not terminate. Multiplying the fraction by any number of powers of 10 424 // will not cancel the demoniator. (Recall the fraction was in lowest terms to start 425 // with.) Otherwise the powers of 10 we need to cancel the denominator is the larger of 426 // powersOfTwo and powersOfFive. 427 if (!den.equals(BigInteger.ONE) && !den.equals(BIG_MINUS_ONE)) { 428 return Integer.MAX_VALUE; 429 } 430 return Math.max(powersOfTwo, powersOfFive); 431 } 432 } 433