1 package org.unicode.cldr.util; 2 3 import com.google.common.base.Splitter; 4 import com.google.common.collect.ImmutableList; 5 import com.google.common.collect.ImmutableMap; 6 import com.google.common.math.BigIntegerMath; 7 import com.ibm.icu.number.LocalizedNumberFormatter; 8 import com.ibm.icu.number.Notation; 9 import com.ibm.icu.number.NumberFormatter; 10 import com.ibm.icu.number.NumberFormatter.GroupingStrategy; 11 import com.ibm.icu.number.Precision; 12 import com.ibm.icu.text.UnicodeSet; 13 import com.ibm.icu.util.Freezable; 14 import com.ibm.icu.util.ICUException; 15 import com.ibm.icu.util.Output; 16 import java.math.BigDecimal; 17 import java.math.BigInteger; 18 import java.math.MathContext; 19 import java.math.RoundingMode; 20 import java.util.ArrayList; 21 import java.util.HashMap; 22 import java.util.LinkedHashMap; 23 import java.util.List; 24 import java.util.Locale; 25 import java.util.Map; 26 import java.util.Objects; 27 import java.util.regex.Pattern; 28 29 /** 30 * Basic class for rational numbers. There is little attempt to optimize, since it will just be used 31 * for testing and data production within CLDR. 32 * 33 * @author markdavis 34 */ 35 public final class Rational extends Number implements Comparable<Rational> { 36 private static final long serialVersionUID = 1L; 37 private static final Pattern INT_POWER_10 = Pattern.compile("10*"); 38 public final BigInteger numerator; 39 public final BigInteger denominator; 40 41 static final BigInteger BI_TWO = BigInteger.valueOf(2); 42 static final BigInteger BI_FIVE = BigInteger.valueOf(5); 43 static final BigInteger BI_MINUS_ONE = BigInteger.valueOf(-1); 44 static final BigInteger BI_TEN = BigInteger.valueOf(10); 45 46 static final BigDecimal BD_TWO = BigDecimal.valueOf(2); 47 static final BigDecimal BD_FIVE = BigDecimal.valueOf(5); 48 49 // Constraints: 50 // always stored in normalized form. 51 // no common factor > 1 (reduced) 52 // denominator never negative 53 // if numerator is zero, denominator is 1 or 0 54 // if denominator is zero, numerator is 1, -1, or 0 55 56 // NOTE, the constructor doesn't do any checking, so everything other than these goes 57 // through Rational.of(...) 58 public static final Rational ZERO = new Rational(BigInteger.ZERO, BigInteger.ONE); 59 public static final Rational ONE = new Rational(BigInteger.ONE, BigInteger.ONE); 60 public static final Rational NaN = new Rational(BigInteger.ZERO, BigInteger.ZERO); 61 public static final Rational INFINITY = new Rational(BigInteger.ONE, BigInteger.ZERO); 62 63 public static final Rational NEGATIVE_ONE = ONE.negate(); 64 public static final Rational NEGATIVE_INFINITY = INFINITY.negate(); 65 66 public static final Rational TWO = new Rational(BI_TWO, BigInteger.ONE); 67 public static final Rational TEN = new Rational(BI_TEN, BigInteger.ONE); 68 public static final Rational TENTH = TEN.reciprocal(); 69 70 public static final char REPTEND_MARKER = '˙'; 71 public static final String APPROX = "~"; 72 73 public static class RationalParser implements Freezable<RationalParser> { 74 75 public static final RationalParser BASIC = new RationalParser().freeze(); 76 77 private static final Splitter slashSplitter = Splitter.on('/').trimResults(); 78 private static final Splitter starSplitter = Splitter.on('*').trimResults(); 79 80 private Map<String, Rational> constants; 81 private Map<String, String> constantStatus; 82 RationalParser()83 public RationalParser() { 84 constants = new LinkedHashMap<>(); 85 constantStatus = new LinkedHashMap<>(); 86 } 87 RationalParser( Map<String, Rational> constants2, Map<String, String> constantStatus2)88 public RationalParser( 89 Map<String, Rational> constants2, Map<String, String> constantStatus2) { 90 frozen = false; 91 constants = new LinkedHashMap<>(constants2); 92 constantStatus = new LinkedHashMap<>(constantStatus2); 93 } 94 addConstant(String id, String value, String status)95 public RationalParser addConstant(String id, String value, String status) { 96 final Rational parsed = parse(value); 97 if (constants.put(id, parsed) != null) { 98 throw new IllegalArgumentException("Can't reset constant " + id + " = " + value); 99 } 100 if (status != null) { 101 constantStatus.put(id, status); 102 } 103 return this; 104 } 105 106 /* 107 * input = comp (/ comp)? 108 * comp = comp2 (* comp2)* 109 * comp2 = digits (. digits)? | constant 110 * */ 111 parse(String input)112 public Rational parse(String input) { 113 switch (input) { 114 case "NaN": 115 return NaN; 116 case "INF": 117 return INFINITY; 118 case "-INF": 119 return NEGATIVE_INFINITY; 120 } 121 if (input.startsWith(APPROX)) { 122 input = input.substring(1); 123 } 124 input = input.replace(",", ""); // allow commas anywhere 125 List<String> comps = slashSplitter.splitToList(input); // get num/den 126 try { 127 switch (comps.size()) { 128 case 1: 129 return process(comps.get(0)); 130 case 2: 131 return process(comps.get(0)).divide(process(comps.get(1))); 132 default: 133 throw new IllegalArgumentException("too many slashes in " + input); 134 } 135 } catch (Exception e) { 136 throw new ICUException("bad input: " + input, e); 137 } 138 } 139 process(String string)140 private Rational process(String string) { 141 Rational result = null; 142 string = string.replace(HUMAN_EXPONENT, "E"); 143 for (String comp : starSplitter.split(string)) { 144 Rational ratComp = process2(comp); 145 result = result == null ? ratComp : result.multiply(ratComp); 146 } 147 return result; 148 } 149 150 static final UnicodeSet ALLOWED_CHARS = 151 new UnicodeSet("[-A-Za-z0-9_]").add(REPTEND_MARKER).freeze(); 152 process2(String input)153 private Rational process2(String input) { 154 final char firstChar = input.charAt(0); 155 if (firstChar == '-' || (firstChar >= '0' && firstChar <= '9')) { 156 int pos = input.indexOf(REPTEND_MARKER); 157 if (pos < 0) { 158 return Rational.of(new BigDecimal(input)); 159 } 160 // handle repeating fractions 161 String reptend = input.substring(pos + 1); 162 int rlen = reptend.length(); 163 input = input.substring(0, pos) + reptend; 164 165 BigDecimal rf = new BigDecimal(input); 166 BigDecimal rfPow = new BigDecimal(input + reptend).scaleByPowerOfTen(rlen); 167 BigDecimal num = rfPow.subtract(rf); 168 169 Rational result = Rational.of(num); 170 Rational den = 171 Rational.of( 172 BigDecimal.ONE 173 .scaleByPowerOfTen(rlen) 174 .subtract(BigDecimal.ONE)); // could optimize 175 return result.divide(den); 176 } else { 177 if (!ALLOWED_CHARS.containsAll(input)) { 178 throw new IllegalArgumentException("Bad characters in: " + input); 179 } 180 Rational result = constants.get(input); 181 if (result == null) { 182 throw new IllegalArgumentException("Constant not defined: " + input); 183 } 184 return result; 185 } 186 } 187 188 boolean frozen = false; 189 190 @Override isFrozen()191 public boolean isFrozen() { 192 return frozen; 193 } 194 195 @Override freeze()196 public RationalParser freeze() { 197 if (!frozen) { 198 frozen = true; 199 constants = ImmutableMap.copyOf(constants); 200 constantStatus = ImmutableMap.copyOf(constantStatus); 201 } 202 return this; 203 } 204 205 @Override cloneAsThawed()206 public RationalParser cloneAsThawed() { 207 return new RationalParser(constants, constantStatus); 208 } 209 getConstants()210 public Map<String, Rational> getConstants() { 211 return constants; 212 } 213 } 214 of(long numerator, long denominator)215 public static Rational of(long numerator, long denominator) { 216 return Rational.of(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator)); 217 } 218 of(long numerator)219 public static Rational of(long numerator) { 220 return Rational.of(BigInteger.valueOf(numerator), BigInteger.ONE); 221 } 222 of(BigInteger numerator, BigInteger denominator)223 public static Rational of(BigInteger numerator, BigInteger denominator) { 224 int dComparison = denominator.compareTo(BigInteger.ZERO); 225 if (dComparison == 0) { 226 // catch equivalents to NaN, -INF, +INF 227 // 0/0 => NaN 228 // +/0 => INF 229 // -/0 => -INF 230 int nComparison = numerator.compareTo(BigInteger.ZERO); 231 return nComparison < 0 ? NEGATIVE_INFINITY : nComparison > 0 ? INFINITY : NaN; 232 } else { 233 // reduce to lowest form 234 BigInteger gcd = numerator.gcd(denominator); 235 if (gcd.compareTo(BigInteger.ONE) > 0) { 236 numerator = numerator.divide(gcd); 237 denominator = denominator.divide(gcd); 238 } 239 if (dComparison < 0) { 240 // ** NOTE: is already reduced, so safe to use constructor 241 return new Rational(numerator, denominator); 242 } else { 243 // ** NOTE: is already reduced, so safe to use constructor 244 return new Rational(numerator.negate(), denominator.negate()); 245 } 246 } 247 } 248 of(BigInteger numerator)249 public static Rational of(BigInteger numerator) { 250 // ** NOTE: is already reduced, so safe to use constructor 251 return new Rational(numerator, BigInteger.ONE); 252 } 253 of(String simple)254 public static Rational of(String simple) { 255 return RationalParser.BASIC.parse(simple); 256 } 257 Rational(BigInteger numerator, BigInteger denominator)258 private Rational(BigInteger numerator, BigInteger denominator) { 259 if (denominator.compareTo(BigInteger.ZERO) < 0) { 260 numerator = numerator.negate(); 261 denominator = denominator.negate(); 262 } 263 BigInteger gcd = numerator.gcd(denominator); 264 if (gcd.compareTo(BigInteger.ONE) > 0) { 265 numerator = numerator.divide(gcd); 266 denominator = denominator.divide(gcd); 267 } 268 this.numerator = numerator; 269 this.denominator = denominator; 270 } 271 add(Rational other)272 public Rational add(Rational other) { 273 BigInteger newNumerator = 274 numerator.multiply(other.denominator).add(other.numerator.multiply(denominator)); 275 BigInteger newDenominator = denominator.multiply(other.denominator); 276 return Rational.of(newNumerator, newDenominator); 277 } 278 subtract(Rational other)279 public Rational subtract(Rational other) { 280 BigInteger newNumerator = 281 numerator 282 .multiply(other.denominator) 283 .subtract(other.numerator.multiply(denominator)); 284 BigInteger newDenominator = denominator.multiply(other.denominator); 285 return Rational.of(newNumerator, newDenominator); 286 } 287 multiply(Rational other)288 public Rational multiply(Rational other) { 289 BigInteger newNumerator = numerator.multiply(other.numerator); 290 BigInteger newDenominator = denominator.multiply(other.denominator); 291 return Rational.of(newNumerator, newDenominator); 292 } 293 divide(Rational other)294 public Rational divide(Rational other) { 295 BigInteger newNumerator = numerator.multiply(other.denominator); 296 BigInteger newDenominator = denominator.multiply(other.numerator); 297 return Rational.of(newNumerator, newDenominator); 298 } 299 pow(int i)300 public Rational pow(int i) { 301 return Rational.of(numerator.pow(i), denominator.pow(i)); 302 } 303 pow10(int i)304 public static Rational pow10(int i) { 305 return i > 0 ? TEN.pow(i) : TENTH.pow(-i); 306 } 307 reciprocal()308 public Rational reciprocal() { 309 return Rational.of(denominator, numerator); 310 } 311 negate()312 public Rational negate() { 313 // ** NOTE: is already reduced, so safe to use constructor 314 return new Rational(numerator.negate(), denominator); 315 } 316 toBigDecimal(MathContext mathContext)317 public BigDecimal toBigDecimal(MathContext mathContext) { 318 try { 319 return new BigDecimal(numerator).divide(new BigDecimal(denominator), mathContext); 320 } catch (Exception e) { 321 throw new IllegalArgumentException( 322 "Wrong math context for divide: " + this + ", " + mathContext); 323 } 324 } 325 toBigDecimal()326 public BigDecimal toBigDecimal() { 327 return toBigDecimal(MathContext.DECIMAL128); // prevent failures due to repeating fractions 328 } 329 330 @Override doubleValue()331 public double doubleValue() { 332 if (denominator.equals(BigInteger.ZERO) && numerator.equals(BigInteger.ZERO)) { 333 return Double.NaN; 334 } 335 return toBigDecimal(MathContext.DECIMAL64).doubleValue(); 336 } 337 338 @Override floatValue()339 public float floatValue() { 340 if (denominator.equals(BigInteger.ZERO) && numerator.equals(BigInteger.ZERO)) { 341 return Float.NaN; 342 } 343 return toBigDecimal(MathContext.DECIMAL32).floatValue(); 344 } 345 of(BigDecimal bigDecimal)346 public static Rational of(BigDecimal bigDecimal) { 347 // scale() 348 // If zero or positive, the scale is the number of digits to the right of the decimal point. 349 // If negative, the unscaled value of the number is multiplied by ten to the power of the 350 // negation of the scale. 351 // For example, a scale of -3 means the unscaled value is multiplied by 1000. 352 final int scale = bigDecimal.scale(); 353 final BigInteger unscaled = bigDecimal.unscaledValue(); 354 if (scale == 0) { 355 // ** NOTE: is already reduced, so safe to use constructor 356 return new Rational(unscaled, BigInteger.ONE); 357 } else if (scale >= 0) { 358 return Rational.of(unscaled, BigDecimal.ONE.movePointRight(scale).toBigInteger()); 359 } else { 360 // ** NOTE: is already reduced, so safe to use constructor 361 return new Rational( 362 unscaled.multiply(BigDecimal.ONE.movePointLeft(scale).toBigInteger()), 363 BigInteger.ONE); 364 } 365 } 366 of(double doubleValue)367 public static Rational of(double doubleValue) { 368 return of(new BigDecimal(doubleValue)); 369 } 370 371 public enum FormatStyle { 372 /** 373 * Simple numerator / denominator, plain BigInteger.toString(), dropping " / 1". <br> 374 * The spaces are thin space (2009). 375 */ 376 plain, 377 /** 378 * Approximate value if small number of digits, dropping " / 1" <br> 379 * The spaces are thin space (2009). 380 */ 381 approx, 382 /** 383 * Repeating decimal where possible (otherwise = simple). The limit is 30 repeating digits. 384 */ 385 repeating, 386 /** 387 * Repeating decimal where possible (otherwise = simple). The limit is 1000 repeating 388 * digits. 389 */ 390 repeatingAll, 391 /** 392 * Formatted numerator / denominator, dropping " / 1" <br> 393 * The spaces are thin space (2009). 394 */ 395 formatted, 396 /** HTML Formatted numerator / denominator, using sup/sub, dropping "/1" */ 397 html 398 } 399 400 @Override toString()401 public String toString() { 402 // could also return as "exact" decimal, if only factors of the denominator are 2 and 5 403 return toString(FormatStyle.plain); 404 } 405 406 private static final BigInteger NUM_OR_DEN_LIMIT = BigInteger.valueOf(10000000); 407 private static final BigInteger NUM_TIMES_DEN_LIMIT = BigInteger.valueOf(10000000); 408 private static final BigInteger BIG_HIGH = BigInteger.valueOf(10000000); 409 private static final double DOUBLE_HIGH = 10000000d; 410 private static final double DOUBLE_LOW = 0.001d; 411 412 static final String THIN_SPACE = "\u2009"; 413 static final String DIVIDER = "/"; 414 toString(FormatStyle style)415 public String toString(FormatStyle style) { 416 boolean denIsOne = denominator.equals(BigInteger.ONE); 417 BigInteger absNumerator = numerator.abs(); 418 String result; 419 switch (style) { 420 case plain: 421 result = numerator + (denIsOne ? "" : DIVIDER + denominator); 422 break; 423 case approx: 424 if (denIsOne) { 425 if (absNumerator.compareTo(BIG_HIGH) < 0) { 426 result = formatGroup.format(numerator).toString(); 427 } else { 428 result = replaceE(formatSciSigDig5.format(numerator).toString()); 429 } 430 } else { 431 int log10num = BigIntegerMath.log10(absNumerator, RoundingMode.UP); 432 int log10den = BigIntegerMath.log10(denominator, RoundingMode.UP); 433 if ((log10num <= 1 && log10den <= 4) || (log10num <= 4 && log10den <= 1)) { 434 result = 435 formatGroup.format(numerator) 436 + DIVIDER 437 + formatNoGroup.format(denominator); 438 } else { 439 final double doubleValue = 440 numerator.doubleValue() / denominator.doubleValue(); 441 double absDoubleValue = Math.abs(doubleValue); 442 if (DOUBLE_LOW < absDoubleValue && absDoubleValue < DOUBLE_HIGH) { 443 result = formatSigDig5.format(doubleValue).toString(); 444 } else { 445 result = formatSciSigDig5.format(doubleValue).toString(); 446 } 447 } 448 } 449 break; 450 default: 451 Output<BigDecimal> newNumerator = new Output<>(new BigDecimal(numerator)); 452 final BigInteger newDenominator = minimalDenominator(newNumerator, denominator); 453 final String numStr = formatGroup.format(newNumerator.value).toString(); 454 final String denStr = formatNoGroup.format(newDenominator).toString(); 455 denIsOne = newDenominator.equals(BigInteger.ONE); 456 int limit = 1000; 457 458 switch (style) { 459 case repeating: 460 limit = 30; 461 // fall through with smaller limit 462 case repeatingAll: 463 // if we come directly here, the limit is huge 464 result = toRepeating(limit); 465 if (result != null) { // null is returned if we can't fit into the limit 466 break; 467 } 468 // otherwise drop through to simple 469 case formatted: 470 // skip approximate test 471 result = denIsOne ? numStr : numStr + DIVIDER + denStr; 472 break; 473 case html: 474 // skip approximate test 475 return denIsOne 476 ? numStr 477 : "<sup>" + numStr + "</sup>" + "/<sub>" + denStr + "<sub>"; 478 default: 479 throw new UnsupportedOperationException(); 480 } 481 } 482 Rational roundtrip = Rational.of(result); 483 return replaceE(roundtrip.equals(this) ? result : APPROX + result); 484 } 485 486 static final String HUMAN_EXPONENT = "×10ˆ"; 487 replaceE(String format2)488 private String replaceE(String format2) { 489 return format2.replace("E", HUMAN_EXPONENT); 490 } 491 492 private static final LocalizedNumberFormatter formatGroup = 493 NumberFormatter.with().precision(Precision.unlimited()).locale(Locale.ENGLISH); 494 495 private static final LocalizedNumberFormatter formatNoGroup = 496 NumberFormatter.with() 497 .precision(Precision.unlimited()) 498 .grouping(GroupingStrategy.OFF) 499 .locale(Locale.ENGLISH); 500 501 private static final LocalizedNumberFormatter formatSigDig5 = 502 NumberFormatter.with() 503 .precision(Precision.maxSignificantDigits(5)) 504 .locale(Locale.ENGLISH); 505 506 private static final LocalizedNumberFormatter formatSciSigDig5 = 507 NumberFormatter.with() 508 .precision(Precision.maxSignificantDigits(5)) 509 .notation(Notation.engineering()) 510 .locale(Locale.ENGLISH); 511 512 @Override compareTo(Rational other)513 public int compareTo(Rational other) { 514 return numerator 515 .multiply(other.denominator) 516 .compareTo(other.numerator.multiply(denominator)); 517 } 518 519 @Override equals(Object that)520 public boolean equals(Object that) { 521 return equals((Rational) that); // TODO fix later 522 } 523 equals(Rational that)524 public boolean equals(Rational that) { 525 return numerator.equals(that.numerator) && denominator.equals(that.denominator); 526 } 527 528 @Override hashCode()529 public int hashCode() { 530 return Objects.hash(numerator, denominator); 531 } 532 abs()533 public Rational abs() { 534 return numerator.signum() >= 0 ? this : this.negate(); 535 } 536 537 /** 538 * Goal is to be able to display rationals in a short but exact form, like 1,234,567/3 or 539 * 1.234567E21/3. To do this, find the smallest denominator (excluding powers of 2 and 5), and 540 * modify the numerator in the same way. 541 * 542 * @param denominator TODO 543 * @param current 544 * @return 545 */ minimalDenominator( Output<BigDecimal> outNumerator, BigInteger denominator)546 public static BigInteger minimalDenominator( 547 Output<BigDecimal> outNumerator, BigInteger denominator) { 548 if (denominator.equals(BigInteger.ONE) || denominator.equals(BigInteger.ZERO)) { 549 return denominator; 550 } 551 BigInteger newDenominator = denominator; 552 while (newDenominator.mod(BI_TWO).equals(BigInteger.ZERO)) { 553 newDenominator = newDenominator.divide(BI_TWO); 554 outNumerator.value = outNumerator.value.divide(BD_TWO); 555 } 556 BigInteger outDenominator = newDenominator; 557 while (newDenominator.mod(BI_FIVE).equals(BigInteger.ZERO)) { 558 newDenominator = newDenominator.divide(BI_FIVE); 559 outNumerator.value = outNumerator.value.divide(BD_FIVE); 560 } 561 return newDenominator; 562 } 563 564 public static class MutableLong { 565 public long value; 566 567 @Override toString()568 public String toString() { 569 return String.valueOf(value); 570 } 571 } 572 573 public static class ContinuedFraction { 574 public final List<BigInteger> sequence; 575 ContinuedFraction(Rational source)576 public ContinuedFraction(Rational source) { 577 List<BigInteger> _sequence = new ArrayList<>(); 578 while (true) { 579 BigInteger floor = source.floor(); 580 if (floor.compareTo(BigInteger.ZERO) < 0) { 581 floor = floor.subtract(BigInteger.ONE); 582 } 583 Rational remainder = source.subtract(Rational.of(floor, BigInteger.ONE)); 584 _sequence.add(floor); 585 if (remainder.equals(Rational.ZERO)) { 586 break; 587 } 588 source = remainder.reciprocal(); 589 } 590 sequence = ImmutableList.copyOf(_sequence); 591 } 592 ContinuedFraction(long... items)593 public ContinuedFraction(long... items) { 594 List<BigInteger> _sequence = new ArrayList<>(); 595 int count = 0; 596 for (long item : items) { 597 if (count != 0 && item < 0) { 598 throw new IllegalArgumentException("Only first item can be negative"); 599 } 600 _sequence.add(BigInteger.valueOf(item)); 601 count++; 602 } 603 sequence = ImmutableList.copyOf(_sequence); 604 } 605 toRational(List<Rational> intermediates)606 public Rational toRational(List<Rational> intermediates) { 607 if (intermediates != null) { 608 intermediates.clear(); 609 } 610 BigInteger h0 = BigInteger.ZERO; 611 BigInteger h1 = BigInteger.ONE; 612 BigInteger k0 = BigInteger.ONE; 613 BigInteger k1 = BigInteger.ZERO; 614 for (BigInteger item : sequence) { 615 BigInteger h2 = item.multiply(h1).add(h0); 616 BigInteger k2 = item.multiply(k1).add(k0); 617 if (intermediates != null) { 618 intermediates.add(Rational.of(h2, k2)); 619 } 620 h0 = h1; 621 h1 = h2; 622 k0 = k1; 623 k1 = k2; 624 } 625 if (intermediates != null) { 626 Rational last = intermediates.get(intermediates.size() - 1); 627 intermediates.remove(intermediates.size() - 1); 628 return last; 629 } else { 630 return Rational.of(h1, k1); 631 } 632 } 633 634 @Override toString()635 public String toString() { 636 return sequence.toString(); 637 } 638 639 @Override equals(Object obj)640 public boolean equals(Object obj) { 641 return sequence.equals(((ContinuedFraction) obj).sequence); 642 } 643 644 @Override hashCode()645 public int hashCode() { 646 return sequence.hashCode(); 647 } 648 } 649 floor()650 public BigInteger floor() { 651 return numerator.divide(denominator); 652 } 653 654 /** The symmetric difference of a and b is 2 * abs(a - b) / (a + b) */ symmetricDiff(Rational b)655 public Rational symmetricDiff(Rational b) { 656 return equals(b) ? ZERO : subtract(b).abs().multiply(TWO).divide(add(b)); 657 } 658 659 /** Return repeating fraction, as long as the length is reasonable */ toRepeating(int stringLimit)660 private String toRepeating(int stringLimit) { 661 BigInteger p = numerator; 662 BigInteger q = denominator; 663 StringBuilder s = new StringBuilder(); 664 665 // Edge cases 666 final int pTo0 = p.compareTo(BigInteger.ZERO); 667 if (q.compareTo(BigInteger.ZERO) == 0) { 668 return pTo0 == 0 ? "NaN" : pTo0 > 0 ? "INF" : "-INF"; 669 } 670 if (pTo0 == 0) { 671 return "0"; 672 } else if (pTo0 < 0) { 673 p = p.negate(); 674 s.append('-'); 675 } 676 final int pToq = p.compareTo(q); 677 if (pToq == 0) { 678 s.append('1'); 679 return s.toString(); 680 } else if (pToq > 0) { 681 BigInteger intPart = p.divide(q); 682 s.append(formatNoGroup.format(intPart)); 683 p = p.remainder(q); 684 if (p.compareTo(BigInteger.ZERO) == 0) { 685 return s.toString(); 686 } 687 } else { 688 s.append('0'); 689 } 690 691 // main loop 692 s.append("."); 693 int pos = -1; // all places are right to the radix point 694 Map<BigInteger, Integer> occurs = new HashMap<>(); 695 while (!occurs.containsKey(p)) { 696 occurs.put(p, pos); // the position of the place with remainder p 697 BigInteger p10 = p.multiply(BigInteger.TEN); 698 BigInteger z = p10.divide(q); // index z of digit within: 0 ≤ z ≤ b-1 699 p = p10.subtract(z.multiply(q)); // 0 ≤ p < q 700 s = s.append(((char) ('0' + z.intValue()))); // append the character of the digit 701 if (p.equals(BigInteger.ZERO)) { 702 return s.toString(); 703 } else if (s.length() > stringLimit) { 704 return null; 705 } 706 pos -= 1; 707 } 708 int L = occurs.get(p) - pos; // the length of the reptend (being < q) 709 s.insert(s.length() - L, REPTEND_MARKER); 710 return s.toString(); 711 } 712 isPowerOfTen()713 public boolean isPowerOfTen() { 714 Output<BigDecimal> newNumerator = new Output<>(new BigDecimal(numerator)); 715 final BigInteger newDenominator = minimalDenominator(newNumerator, denominator); 716 if (!newDenominator.equals(BigInteger.ONE)) { 717 return false; 718 } 719 // hack, figure out later 720 String str = newNumerator.value.unscaledValue().toString(); 721 if (INT_POWER_10.matcher(str).matches()) { 722 return true; 723 } 724 return false; 725 } 726 getIntPowerOfTen()727 public String getIntPowerOfTen() { 728 // HACK, figure out better later 729 if (numerator.compareTo(BigInteger.ZERO) < 0) { 730 throw new IllegalArgumentException("Prefix label must be positive: " + this); 731 } 732 if (!numerator.equals(BigInteger.ONE) && !denominator.equals(BigInteger.ONE)) { 733 throw new IllegalArgumentException("Prefix label must be power of 10: " + this); 734 } 735 BigInteger value; 736 int sign; 737 if (numerator.equals(BigInteger.ONE)) { 738 value = denominator; 739 sign = -1; 740 } else { 741 value = numerator; 742 sign = 1; 743 } 744 String str = value.toString(); 745 if (!INT_POWER_10.matcher(str).matches()) { 746 throw new IllegalArgumentException("Prefix label must be power of 10: " + this); 747 } 748 int result = str.length() - 1; 749 return String.valueOf(result * sign); 750 } 751 752 public static final Rational EPSILON = Rational.of(1, 1000000); 753 754 /** Approximately equal when symmetric difference of a and b < epsilon (default EPSILON) */ approximatelyEquals(Rational b, Rational epsilon)755 public boolean approximatelyEquals(Rational b, Rational epsilon) { 756 return symmetricDiff(b).compareTo(epsilon) < 0; 757 } 758 approximatelyEquals(Rational b)759 public boolean approximatelyEquals(Rational b) { 760 return approximatelyEquals(b, EPSILON); 761 } 762 approximatelyEquals(Number b)763 public boolean approximatelyEquals(Number b) { 764 return approximatelyEquals(Rational.of(b.doubleValue()), EPSILON); 765 } 766 767 @Override intValue()768 public int intValue() { 769 return toBigDecimal().intValue(); 770 } 771 772 @Override longValue()773 public long longValue() { 774 return toBigDecimal().longValue(); 775 } 776 } 777