1 package org.unicode.cldr.util; 2 3 import java.math.BigDecimal; 4 import java.math.BigInteger; 5 import java.math.MathContext; 6 import java.util.ArrayList; 7 import java.util.HashMap; 8 import java.util.LinkedHashMap; 9 import java.util.List; 10 import java.util.Locale; 11 import java.util.Map; 12 import java.util.Objects; 13 import java.util.regex.Pattern; 14 15 import com.google.common.base.Splitter; 16 import com.google.common.collect.ImmutableList; 17 import com.google.common.collect.ImmutableMap; 18 import com.ibm.icu.number.LocalizedNumberFormatter; 19 import com.ibm.icu.number.Notation; 20 import com.ibm.icu.number.NumberFormatter; 21 import com.ibm.icu.number.Precision; 22 import com.ibm.icu.text.UnicodeSet; 23 import com.ibm.icu.util.Freezable; 24 import com.ibm.icu.util.ICUException; 25 import com.ibm.icu.util.Output; 26 27 /** 28 * Very basic class for rational numbers. No attempt to optimize, since it will just 29 * be used for testing within CLDR. 30 * 31 * @author markdavis 32 * 33 */ 34 public final class Rational implements Comparable<Rational> { 35 private static final Pattern INT_POWER_10 = Pattern.compile("10*"); 36 public final BigInteger numerator; 37 public final BigInteger denominator; 38 39 // Constraints: 40 // always stored in normalized form. 41 // no common factor > 1 (reduced) 42 // denominator never negative 43 // if numerator is zero, denominator is 1 or 0 44 // if denominator is zero, numerator is 1, -1, or 0 45 46 public static final Rational ZERO = Rational.of(0); 47 public static final Rational ONE = Rational.of(1); 48 public static final Rational NEGATIVE_ONE = ONE.negate(); 49 50 public static final Rational INFINITY = Rational.of(1,0); 51 public static final Rational NEGATIVE_INFINITY = INFINITY.negate(); 52 public static final Rational NaN = Rational.of(0,0); 53 54 public static final Rational TEN = Rational.of(10, 1); 55 public static final Rational TENTH = TEN.reciprocal(); 56 57 public static final char REPTEND_MARKER = '˙'; 58 59 public static class RationalParser implements Freezable<RationalParser>{ 60 61 public static RationalParser BASIC = new RationalParser().freeze(); 62 63 private static Splitter slashSplitter = Splitter.on('/').trimResults(); 64 private static Splitter starSplitter = Splitter.on('*').trimResults(); 65 66 private Map<String,Rational> constants = new LinkedHashMap<>(); 67 private Map<String,String> constantStatus = new LinkedHashMap<>(); 68 addConstant(String id, String value, String status)69 public RationalParser addConstant(String id, String value, String status) { 70 if (constants.put(id, parse(value)) != null) { 71 throw new IllegalArgumentException("Can't reset constant " + id + " = " + value); 72 } 73 if (status != null) { 74 constantStatus.put(id, status); 75 } 76 return this; 77 } 78 79 /* 80 * input = comp (/ comp)? 81 * comp = comp2 (* comp2)* 82 * comp2 = digits (. digits)? | constant 83 * */ 84 parse(String input)85 public Rational parse(String input) { 86 switch (input) { 87 case "NaN" : return NaN; 88 case "INF" : return INFINITY; 89 case "-INF": return NEGATIVE_INFINITY; 90 } 91 List<String> comps = slashSplitter.splitToList(input.replace(",", "")); // allow commas anywhere 92 try { 93 switch (comps.size()) { 94 case 1: return process(comps.get(0)); 95 case 2: return process(comps.get(0)).divide(process(comps.get(1))); 96 default: throw new IllegalArgumentException("too many slashes in " + input); 97 } 98 } catch (Exception e) { 99 throw new ICUException("bad input: " + input, e); 100 } 101 } 102 process(String string)103 private Rational process(String string) { 104 Rational result = null; 105 for (String comp : starSplitter.split(string)) { 106 Rational ratComp = process2(comp); 107 result = result == null ? ratComp : result.multiply(ratComp); 108 } 109 return result; 110 } 111 112 static final UnicodeSet ALLOWED_CHARS = new UnicodeSet("[-A-Za-z0-9_]").add(REPTEND_MARKER).freeze(); 113 process2(String input)114 private Rational process2(String input) { 115 final char firstChar = input.charAt(0); 116 if (firstChar == '-' || (firstChar >= '0' && firstChar <= '9')) { 117 int pos = input.indexOf(REPTEND_MARKER); 118 if (pos < 0) { 119 return Rational.of(new BigDecimal(input)); 120 } 121 // handle repeating fractions 122 String reptend = input.substring(pos+1); 123 int rlen = reptend.length(); 124 input = input.substring(0,pos) + reptend; 125 126 BigDecimal rf = new BigDecimal(input); 127 BigDecimal rfPow = new BigDecimal(input+reptend).scaleByPowerOfTen(rlen); 128 BigDecimal num = rfPow.subtract(rf); 129 130 Rational result = Rational.of(num); 131 Rational den = Rational.of(BigDecimal.ONE.scaleByPowerOfTen(rlen).subtract(BigDecimal.ONE)); // could optimize 132 return result.divide(den); 133 } else { 134 if (!ALLOWED_CHARS.containsAll(input)) { 135 throw new IllegalArgumentException("Bad characters in: " + input); 136 } 137 Rational result = constants.get(input); 138 if (result == null) { 139 throw new IllegalArgumentException("Constant not defined: " + input); 140 } 141 return result; 142 } 143 } 144 145 boolean frozen = false; 146 147 @Override isFrozen()148 public boolean isFrozen() { 149 return frozen; 150 } 151 152 @Override freeze()153 public RationalParser freeze() { 154 if (!frozen) { 155 frozen = true; 156 constants = ImmutableMap.copyOf(constants); 157 constantStatus = ImmutableMap.copyOf(constantStatus); 158 } 159 return this; 160 } 161 162 @Override cloneAsThawed()163 public RationalParser cloneAsThawed() { 164 throw new UnsupportedOperationException(); 165 } 166 getConstants()167 public Map<String, Rational> getConstants() { 168 return constants; 169 } 170 } 171 of(long numerator, long denominator)172 public static Rational of(long numerator, long denominator) { 173 return new Rational(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator)); 174 } 175 of(long numerator)176 public static Rational of(long numerator) { 177 return new Rational(BigInteger.valueOf(numerator), BigInteger.ONE); 178 } 179 of(BigInteger numerator, BigInteger denominator)180 public static Rational of(BigInteger numerator, BigInteger denominator) { 181 return new Rational(numerator, denominator); 182 } 183 of(BigInteger numerator)184 public static Rational of(BigInteger numerator) { 185 return new Rational(numerator, BigInteger.ONE); 186 } 187 of(String simple)188 public static Rational of(String simple) { 189 return RationalParser.BASIC.parse(simple); 190 } 191 Rational(BigInteger numerator, BigInteger denominator)192 private Rational(BigInteger numerator, BigInteger denominator) { 193 if (denominator.compareTo(BigInteger.ZERO) < 0) { 194 numerator = numerator.negate(); 195 denominator = denominator.negate(); 196 } 197 BigInteger gcd = numerator.gcd(denominator); 198 if (gcd.compareTo(BigInteger.ONE) > 0) { 199 numerator = numerator.divide(gcd); 200 denominator = denominator.divide(gcd); 201 } 202 this.numerator = numerator; 203 this.denominator = denominator; 204 } 205 add(Rational other)206 public Rational add(Rational other) { 207 BigInteger gcd_den = denominator.gcd(other.denominator); 208 return new Rational( 209 numerator.multiply(other.denominator).divide(gcd_den) 210 .add(other.numerator.multiply(denominator).divide(gcd_den)), 211 denominator.multiply(other.denominator).divide(gcd_den) 212 ); 213 } 214 subtract(Rational other)215 public Rational subtract(Rational other) { 216 BigInteger gcd_den = denominator.gcd(other.denominator); 217 return new Rational( 218 numerator.multiply(other.denominator).divide(gcd_den) 219 .subtract(other.numerator.multiply(denominator).divide(gcd_den)), 220 denominator.multiply(other.denominator).divide(gcd_den) 221 ); 222 } 223 multiply(Rational other)224 public Rational multiply(Rational other) { 225 BigInteger gcd_num_oden = numerator.gcd(other.denominator); 226 boolean isZero = gcd_num_oden.equals(BigInteger.ZERO); 227 BigInteger smallNum = isZero ? numerator : numerator.divide(gcd_num_oden); 228 BigInteger smallODen = isZero ? other.denominator : other.denominator.divide(gcd_num_oden); 229 230 BigInteger gcd_den_onum = denominator.gcd(other.numerator); 231 isZero = gcd_den_onum.equals(BigInteger.ZERO); 232 BigInteger smallONum = isZero ? other.numerator : other.numerator.divide(gcd_den_onum); 233 BigInteger smallDen = isZero ? denominator : denominator.divide(gcd_den_onum); 234 235 return new Rational(smallNum.multiply(smallONum), smallDen.multiply(smallODen)); 236 } 237 pow(int i)238 public Rational pow(int i) { 239 return new Rational(numerator.pow(i), denominator.pow(i)); 240 } 241 pow10(int i)242 public static Rational pow10(int i) { 243 return i > 0 ? TEN.pow(i) : TENTH.pow(-i); 244 } 245 divide(Rational other)246 public Rational divide(Rational other) { 247 return multiply(other.reciprocal()); 248 } 249 reciprocal()250 public Rational reciprocal() { 251 return new Rational(denominator, numerator); 252 } 253 negate()254 public Rational negate() { 255 return new Rational(numerator.negate(), denominator); 256 } 257 toBigDecimal(MathContext mathContext)258 public BigDecimal toBigDecimal(MathContext mathContext) { 259 try { 260 return new BigDecimal(numerator).divide(new BigDecimal(denominator), mathContext); 261 } catch (Exception e) { 262 throw new IllegalArgumentException("Wrong math context for divide: " + this + ", " + mathContext); 263 } 264 } 265 doubleValue()266 public double doubleValue() { 267 if (denominator.equals(BigInteger.ZERO) && numerator.equals(BigInteger.ZERO)) { 268 return Double.NaN; 269 } 270 return new BigDecimal(numerator).divide(new BigDecimal(denominator), MathContext.DECIMAL64).doubleValue(); 271 } 272 273 toBigDecimal()274 public BigDecimal toBigDecimal() { 275 return toBigDecimal(MathContext.UNLIMITED); 276 } 277 of(BigDecimal bigDecimal)278 public static Rational of(BigDecimal bigDecimal) { 279 // scale() 280 // If zero or positive, the scale is the number of digits to the right of the decimal point. 281 // If negative, the unscaled value of the number is multiplied by ten to the power of the negation of the scale. 282 // For example, a scale of -3 means the unscaled value is multiplied by 1000. 283 final int scale = bigDecimal.scale(); 284 final BigInteger unscaled = bigDecimal.unscaledValue(); 285 if (scale == 0) { 286 return new Rational(unscaled, BigInteger.ONE); 287 } else if (scale >= 0) { 288 return new Rational(unscaled, BigDecimal.ONE.movePointRight(scale).toBigInteger()); 289 } else { 290 return new Rational(unscaled.multiply(BigDecimal.ONE.movePointLeft(scale).toBigInteger()), BigInteger.ONE); 291 } 292 } 293 294 public enum FormatStyle {plain, simple, repeating, html} 295 296 @Override toString()297 public String toString() { 298 // could also return as "exact" decimal, if only factors of the denominator are 2 and 5 299 return toString(FormatStyle.plain); 300 } 301 302 toString(FormatStyle style)303 public String toString(FormatStyle style) { 304 switch (style) { 305 case plain: 306 return numerator + (denominator.equals(BigInteger.ONE) ? "" : " / " + denominator); 307 } 308 Output<BigDecimal> newNumerator = new Output<>(new BigDecimal(numerator)); 309 final BigInteger newDenominator = minimalDenominator(newNumerator, denominator); 310 final String numStr = format(newNumerator.value); 311 final String denStr = nf.format(newDenominator).toString(); 312 final boolean denIsOne = newDenominator.equals(BigInteger.ONE); 313 314 switch (style) { 315 case repeating: 316 String result = toRepeating(30); // limit of 30 on the repeating length, so we don't get unreasonable 317 if (result != null) { 318 return result; 319 } 320 // otherwise drop through to simple 321 case simple: { 322 return denIsOne ? numStr : numStr + "/" + denStr; 323 } 324 case html: { 325 return denIsOne ? numStr : "<sup>" + numStr + "</sup>" + "/<sub>" + denStr + "<sub>"; 326 } 327 default: throw new UnsupportedOperationException(); 328 } 329 } 330 331 static final LocalizedNumberFormatter nf = NumberFormatter.with().precision(Precision.unlimited()).locale(Locale.ENGLISH); 332 static final LocalizedNumberFormatter snf = NumberFormatter.with().precision(Precision.unlimited()).notation(Notation.engineering()).locale(Locale.ENGLISH); 333 format(BigDecimal value)334 static String format(BigDecimal value) { 335 return nf.format(value).toString(); 336 /* 337 * TODO Change 1000000 to 1×10<sup>-6</sup>, etc. 338 * Only for large/small numbers, eg: 339 * ≥ 1,000,000.0, => 1×10<sup>6</sup> — more than 6 trailing 0's 340 * ≤ 0.0000001 — more than 6 leading zeros 341 * 342 * relevant BigDecimal APIs: 343 * 123.4567 scale: 4 bigint: 1234567 344 * 123456700 scale: 0 bigint: 123456700 345 * 1234567 scale: 0 bigint: 1234567 346 * 0.1234567 scale: 7 bigint: 1234567 347 */ 348 // return (Math.abs(newNumerator.scale()) > 10 ? snf : nf).format(newNumerator).toString(); 349 // Only do this for trailing zeros (above test isn't right) 350 } 351 352 @Override compareTo(Rational other)353 public int compareTo(Rational other) { 354 return numerator.multiply(other.denominator).compareTo(other.numerator.multiply(denominator)); 355 } 356 357 @Override equals(Object that)358 public boolean equals(Object that) { 359 return equals((Rational)that); // TODO fix later 360 } 361 equals(Rational that)362 public boolean equals(Rational that) { 363 return numerator.equals(that.numerator) 364 && denominator.equals(that.denominator); 365 } 366 367 @Override hashCode()368 public int hashCode() { 369 return Objects.hash(numerator, denominator); 370 } 371 abs()372 public Rational abs() { 373 return numerator.signum() >= 0 ? this : this.negate(); 374 } 375 376 static final BigInteger BI_TWO = BigInteger.valueOf(2); 377 static final BigInteger BI_FIVE = BigInteger.valueOf(5); 378 static final BigInteger BI_MINUS_ONE = BigInteger.valueOf(-1); 379 380 static final BigDecimal BD_TWO = BigDecimal.valueOf(2); 381 static final BigDecimal BD_FIVE = BigDecimal.valueOf(5); 382 383 384 /** 385 * Goal is to be able to display rationals in a short but exact form, like 1,234,567/3 or 1.234567E21/3. 386 * To do this, find the smallest denominator (excluding powers of 2 and 5), and modify the numerator in the same way. 387 * @param denominator TODO 388 * @param current 389 * @return 390 */ minimalDenominator(Output<BigDecimal> outNumerator, BigInteger denominator)391 public static BigInteger minimalDenominator(Output<BigDecimal> outNumerator, BigInteger denominator) { 392 if (denominator.equals(BigInteger.ONE) || denominator.equals(BigInteger.ZERO)) { 393 return denominator; 394 } 395 BigInteger newDenominator = denominator; 396 while (newDenominator.mod(BI_TWO).equals(BigInteger.ZERO)) { 397 newDenominator = newDenominator.divide(BI_TWO); 398 outNumerator.value = outNumerator.value.divide(BD_TWO); 399 } 400 BigInteger outDenominator = newDenominator; 401 while (newDenominator.mod(BI_FIVE).equals(BigInteger.ZERO)) { 402 newDenominator = newDenominator.divide(BI_FIVE); 403 outNumerator.value = outNumerator.value.divide(BD_FIVE); 404 } 405 return newDenominator; 406 } 407 408 409 public static class MutableLong { 410 public long value; 411 @Override toString()412 public String toString() { 413 return String.valueOf(value); 414 } 415 } 416 417 public static class ContinuedFraction { 418 public final List<BigInteger> sequence; 419 ContinuedFraction(Rational source)420 public ContinuedFraction(Rational source) { 421 List<BigInteger> _sequence = new ArrayList<>(); 422 while (true) { 423 BigInteger floor = source.floor(); 424 if (floor.compareTo(BigInteger.ZERO) < 0) { 425 floor = floor.subtract(BigInteger.ONE); 426 } 427 Rational remainder = source.subtract(Rational.of(floor, BigInteger.ONE)); 428 _sequence.add(floor); 429 if (remainder.equals(Rational.ZERO)) { 430 break; 431 } 432 source = remainder.reciprocal(); 433 } 434 sequence = ImmutableList.copyOf(_sequence); 435 } 436 ContinuedFraction(long... items)437 public ContinuedFraction(long... items) { 438 List<BigInteger> _sequence = new ArrayList<>(); 439 int count = 0; 440 for (long item : items) { 441 if (count != 0 && item < 0) { 442 throw new IllegalArgumentException("Only first item can be negative"); 443 } 444 _sequence.add(BigInteger.valueOf(item)); 445 count++; 446 } 447 sequence = ImmutableList.copyOf(_sequence); 448 } 449 toRational(List<Rational> intermediates)450 public Rational toRational(List<Rational> intermediates) { 451 if (intermediates != null) { 452 intermediates.clear(); 453 } 454 BigInteger h0 = BigInteger.ZERO; 455 BigInteger h1 = BigInteger.ONE; 456 BigInteger k0 = BigInteger.ONE; 457 BigInteger k1 = BigInteger.ZERO; 458 for (BigInteger item : sequence) { 459 BigInteger h2 = item.multiply(h1).add(h0); 460 BigInteger k2 = item.multiply(k1).add(k0); 461 if (intermediates != null) { 462 intermediates.add(Rational.of(h2, k2)); 463 } 464 h0 = h1; 465 h1 = h2; 466 k0 = k1; 467 k1 = k2; 468 } 469 if (intermediates != null) { 470 Rational last = intermediates.get(intermediates.size()-1); 471 intermediates.remove(intermediates.size()-1); 472 return last; 473 } else { 474 return Rational.of(h1, k1); 475 } 476 } 477 @Override toString()478 public String toString() { 479 return sequence.toString(); 480 } 481 482 @Override equals(Object obj)483 public boolean equals(Object obj) { 484 return sequence.equals(((ContinuedFraction)obj).sequence); 485 } 486 @Override hashCode()487 public int hashCode() { 488 return sequence.hashCode(); 489 } 490 } 491 floor()492 public BigInteger floor() { 493 return numerator.divide(denominator); 494 } 495 symmetricDiff(Rational b)496 public Rational symmetricDiff(Rational b) { 497 return this.subtract(b) 498 .divide(this.abs().add(b.abs())) 499 .multiply(Rational.of(2)) 500 ; 501 } 502 503 /** Return repeating fraction, as long as the length is reasonable */ toRepeating(int stringLimit)504 private String toRepeating(int stringLimit) { 505 BigInteger p = numerator; 506 BigInteger q = denominator; 507 StringBuilder s = new StringBuilder(); 508 509 // Edge cases 510 final int pTo0 = p.compareTo(BigInteger.ZERO); 511 if (q.compareTo(BigInteger.ZERO) == 0) { 512 return pTo0 == 0 ? "NaN" : pTo0 > 0 ? "INF" : "-INF"; 513 } 514 if (pTo0 == 0) { 515 return "0"; 516 } else if (pTo0 < 0) { 517 p = p.negate(); 518 s.append('-'); 519 } 520 final int pToq = p.compareTo(q); 521 if (pToq == 0) { 522 s.append('1'); 523 return s.toString(); 524 } else if (pToq > 0) { 525 BigInteger intPart = p.divide(q); 526 s.append(nf.format(intPart)); 527 p = p.remainder(q); 528 if (p.compareTo(BigInteger.ZERO) == 0) { 529 return s.toString(); 530 } 531 } else { 532 s.append('0'); 533 } 534 535 // main loop 536 s.append("."); 537 int pos = -1; // all places are right to the radix point 538 Map<BigInteger, Integer> occurs = new HashMap<>(); 539 while (!occurs.containsKey(p)) { 540 occurs.put(p, pos); // the position of the place with remainder p 541 BigInteger p10 = p.multiply(BigInteger.TEN); 542 BigInteger z = p10.divide(q); // index z of digit within: 0 ≤ z ≤ b-1 543 p = p10.subtract(z.multiply(q)); // 0 ≤ p < q 544 s = s.append(((char)('0' + z.intValue()))); // append the character of the digit 545 if (p.equals(BigInteger.ZERO)) { 546 return s.toString(); 547 } else if (s.length() > stringLimit) { 548 return null; 549 } 550 pos -= 1; 551 } 552 int L = occurs.get(p)-pos; // the length of the reptend (being < q) 553 s.insert(s.length()-L, REPTEND_MARKER); 554 return s.toString(); 555 } 556 isPowerOfTen()557 public boolean isPowerOfTen() { 558 Output<BigDecimal> newNumerator = new Output<>(new BigDecimal(numerator)); 559 final BigInteger newDenominator = minimalDenominator(newNumerator, denominator); 560 if (!newDenominator.equals(BigInteger.ONE)) { 561 return false; 562 } 563 // hack, figure out later 564 String str = newNumerator.value.unscaledValue().toString(); 565 if (INT_POWER_10.matcher(str).matches()) { 566 return true; 567 } 568 return false; 569 } 570 }