001// Rational.java 002import java.util.Scanner; 003import java.util.Locale; 004import java.util.NoSuchElementException; 005import java.util.Formatter; 006import java.util.Formattable; 007import java.util.regex.Pattern; 008import static java.util.FormattableFlags.ALTERNATE; 009import static java.util.FormattableFlags.LEFT_JUSTIFY; 010import java.util.FormatFlagsConversionMismatchException; 011import java.util.IllegalFormatPrecisionException; 012import java.util.IllegalFormatException; 013 014/** 015 * A rational number is stored as an integer pair (numerator, denominator). 016 * Rational is a value-based class. 017 * @author H.Drachenfels 018 * @version 30.6.2025 019 */ 020public final class Rational extends Number 021 implements Comparable<Rational>, Formattable { 022 023 /** 024 * The number 0 as Rational. 025 */ 026 public static final Rational ZERO = new Rational(0, 1); 027 028 /** 029 * The number 1 as Rational. 030 */ 031 public static final Rational ONE = new Rational(1, 1); 032 033 /** 034 * The largest positive value of type Rational, Integer.MAX_VALUE. 035 */ 036 public static final Rational MAX_VALUE 037 = new Rational(Integer.MAX_VALUE, 1); 038 039 /** 040 * The smallest positive nonzero value of type Rational, 041 * 1 / Integer.MAX_VALUE. 042 */ 043 public static final Rational MIN_VALUE 044 = new Rational(1, Integer.MAX_VALUE); 045 046 /** 047 * The numerator part of the rational number. 048 */ 049 private final int numerator; 050 051 /** 052 * The denominator part of the rational number. 053 * Always greater or equal 1. 054 * The denominator is 1 if numerator is 0. 055 */ 056 private final int denominator; 057 058 /** 059 * Rational number with numerator n and denominator d. 060 * For denominator 0 an IllegalArgumentException is thrown. 061 * @param n the numerator 062 * @param d the denominator (must not be 0) 063 * @return a Rational with the specified value 064 */ 065 public static Rational valueOf(int n, int d) { 066 return new Rational(n, d); 067 } 068 069 /** 070 * Converts an integer to a rational number. 071 * This factory method is provided in preference to an (int) constructor 072 * because it allows for reuse of frequently used Rationals. 073 * @param n the integer to convert 074 * @return a Rational with the specified value 075 */ 076 public static Rational valueOf(int n) { 077 switch (n) { 078 case 0: 079 return ZERO; 080 case 1: 081 return ONE; 082 case Integer.MAX_VALUE: 083 return MAX_VALUE; 084 default: 085 return new Rational(n, 1); 086 } 087 } 088 089 /** 090 * Converts a string to a rational number. 091 * The string must have the format specified by toString. 092 * Otherwise, NumberFormatException is thrown. 093 * @param s the string to convert 094 * @return a Rational with the specified value 095 */ 096 public static Rational valueOf(String s) { 097 Scanner sc = new Scanner(s); 098 if (!sc.hasNext(FORMAT)) { 099 throw new NumberFormatException(s); 100 } 101 102 sc.useDelimiter("/"); 103 return new Rational(sc.nextInt(), sc.nextInt()); 104 } 105 106 private static final Pattern FORMAT = Pattern.compile("^[+-]?\\d+/\\d+$"); 107 108 private Rational(long n, long d) { 109 if (d == 0) { 110 throw new IllegalArgumentException("invalid denominator 0"); 111 } 112 113 if (n == 0) { 114 this.numerator = 0; 115 this.denominator = 1; 116 return; 117 } 118 119 int sign = Long.signum(n) * Long.signum(d); 120 long nn = Math.abs(n); 121 long dd = Math.abs(d); 122 123 if (dd != 1) { 124 long f = gcd(nn, dd); 125 nn /= f; 126 dd /= f; 127 } 128 129 if (nn > Integer.MAX_VALUE) { 130 throw new ArithmeticException( 131 String.format("numerator overflow: %d/%d", sign * nn, dd)); 132 } 133 134 if (dd > Integer.MAX_VALUE) { 135 throw new ArithmeticException( 136 String.format("denominator overflow: %d/%d", sign * nn, dd)); 137 } 138 139 this.numerator = sign * (int) nn; 140 this.denominator = (int) dd; 141 } 142 143 /** 144 * Computes the greates common divisor using Euclid's algorithm. 145 * For negative numbers, the result is undefined. 146 * @param a a positive number 147 * @param b a positive number 148 * @return the gcd of a and b 149 */ 150 private static long gcd(long a, long b) { 151 while (a > 0 && b > 0) { 152 if (a > b) { 153 a = a % b; 154 } else { 155 b = b % a; 156 } 157 } 158 159 return Math.max(a, b); 160 } 161 162 /** 163 * Returns the numerator part of the reduced rational number. 164 * @return the numerator 165 */ 166 public int getNumerator() { 167 return this.numerator; 168 } 169 170 /** 171 * Returns the denominator part of the reduced rational number. 172 * @return the denominator 173 */ 174 public int getDenominator() { 175 return this.denominator; 176 } 177 178 /** 179 * Returns the signum function of this Rational. 180 * @return -1, 0 or 1 as the value of this Rational is 181 * negative, zero or positive. 182 */ 183 public int signum() { 184 return Integer.signum(this.numerator); 185 } 186 187 /** 188 * Returns a Rational with inverted sign. 189 * @return -this 190 */ 191 public Rational negate() { 192 return new Rational(-this.numerator, this.denominator); 193 } 194 195 /** 196 * Returns a Rational with numerator and denominator swapped. 197 * @return 1/this 198 */ 199 public Rational invert() { 200 return new Rational(this.denominator, this.numerator); 201 } 202 203 /** 204 * Returns a Rational whose value is (this + r). 205 * @param r value to be added to this Rational 206 * @return this + r 207 */ 208 public Rational add(Rational r) { 209 if (this.denominator == r.denominator) { 210 return new Rational((long) this.numerator + r.numerator, 211 this.denominator); 212 } 213 214 long n = (long) this.numerator * r.denominator 215 + (long) r.numerator * this.denominator; 216 long d = (long) this.denominator * r.denominator; 217 return new Rational(n, d); 218 } 219 220 /** 221 * Returns a Rational whose value is (this - r). 222 * @param r value to be subtracted from this Rational 223 * @return this - r 224 */ 225 public Rational subtract(Rational r) { 226 if (this.denominator == r.denominator) { 227 return new Rational((long) this.numerator - r.numerator, 228 this.denominator); 229 } 230 231 long n = (long) this.numerator * r.denominator 232 - (long) r.numerator * this.denominator; 233 long d = (long) this.denominator * r.denominator; 234 return new Rational(n, d); 235 } 236 237 /** 238 * Returns a Rational whose value is (this * r). 239 * @param r value to be multiplied by this Rational 240 * @return this * r 241 */ 242 public Rational multiply(Rational r) { 243 long n = (long) this.numerator * r.numerator; 244 long d = (long) this.denominator * r.denominator; 245 return new Rational(n, d); 246 } 247 248 /** 249 * Returns a Rational whose value is (this / r). 250 * @param r value by which this Rational is to be divided 251 * @return this / r 252 */ 253 public Rational divide(Rational r) { 254 if (r.numerator == 0) { 255 throw new IllegalArgumentException("invalid divisor: " + r); 256 } 257 258 long n = (long) this.numerator * r.denominator; 259 long d = (long) this.denominator * r.numerator; 260 return new Rational(n, d); 261 } 262 263 /** 264 * Returns a string representation of this Rational. 265 * The string representation of the numerator, including the sign, 266 * is followed by '/' and the string representation of the denominator. 267 * @return string representation of this Rational. 268 */ 269 @Override 270 public String toString() { 271 return new StringBuilder() 272 .append(this.numerator) 273 .append('/') 274 .append(this.denominator) 275 .toString(); 276 } 277 278 @Override 279 public boolean equals(Object o) { 280 if (o instanceof Rational) { 281 Rational r = (Rational) o; 282 return this.numerator == r.numerator 283 && this.denominator == r.denominator; 284 } 285 286 return false; 287 } 288 289 @Override 290 public int hashCode() { 291 return this.numerator * 31 + this.denominator; 292 } 293 294 @Override 295 public int intValue() { 296 return (int) ((double) this.numerator / this.denominator); 297 } 298 299 @Override 300 public long longValue() { 301 return (long) ((double) this.numerator / this.denominator); 302 } 303 304 @Override 305 public double doubleValue() { 306 return (double) this.numerator / this.denominator; 307 } 308 309 @Override 310 public float floatValue() { 311 return (float) this.doubleValue(); 312 } 313 314 @Override 315 public int compareTo(Rational r) { 316 if (this.denominator == r.denominator) { 317 return Integer.compare(this.numerator, r.numerator); 318 } 319 320 return Long.compare((long) this.numerator * r.denominator, 321 (long) r.numerator * this.denominator); 322 } 323 324 @Override 325 public void formatTo(Formatter f, int flags, int width, int precision) { 326 327 if (precision != -1) { 328 throw new IllegalFormatPrecisionException(precision); 329 } 330 331 if ((flags & ALTERNATE) == ALTERNATE) { 332 throw new FormatFlagsConversionMismatchException("#", 's'); 333 } 334 335 String s = this.toString(); 336 337 if (width <= s.length()) { 338 f.format(s); 339 return; 340 } 341 342 if ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) { 343 f.format("%-" + width + "s", s); 344 return; 345 } 346 347 f.format("%" + width + "s", s); 348 } 349} 350 351/** 352 * Test driver. 353 */ 354final class RationalTest { 355 private RationalTest() { } 356 357 /** 358 * Evaluates an arithmetic postfix expression with rational numbers. 359 * Example: java RationalTest 1/2 3/4 add 5/6 multiply 360 * @param args the arithmetic postfix expression with rational numbers 361 */ 362 public static void main(String[] args) { 363 Rational[] stack = new Rational[args.length]; 364 int top = 0; 365 366 Locale.setDefault(Locale.ENGLISH); 367 Scanner input = new Scanner(System.in); 368 369 for (String arg : args) { 370 try { 371 switch (arg) { 372 case "stackdump": 373 for (int j = 0; j < top; ++j) { 374 System.out.printf("%d: %s (%.15e)%n", 375 j, stack[j], stack[j].doubleValue()); 376 } 377 continue; 378 case "negate": 379 stack[top - 1] = stack[top - 1].negate(); 380 break; 381 case "invert": 382 stack[top - 1] = stack[top - 1].invert(); 383 break; 384 case "add": 385 stack[top - 2] = stack[top - 2].add(stack[top - 1]); 386 --top; 387 break; 388 case "subtract": 389 stack[top - 2] = stack[top - 2].subtract(stack[top - 1]); 390 --top; 391 break; 392 case "multiply": 393 stack[top - 2] = stack[top - 2].multiply(stack[top - 1]); 394 --top; 395 break; 396 case "divide": 397 stack[top - 2] = stack[top - 2].divide(stack[top - 1]); 398 --top; 399 break; 400 401 case "getNumerator": 402 System.out.printf("%s: %d%n", 403 arg, stack[top - 1].getNumerator()); 404 continue; 405 case "getDenominator": 406 System.out.printf("%s: %d%n", 407 arg, stack[top - 1].getDenominator()); 408 continue; 409 case "signum": 410 System.out.printf("%s: %d%n", 411 arg, stack[top - 1].signum()); 412 continue; 413 414 case "toString": 415 System.out.printf("%s: %s%n", 416 arg, stack[top - 1].toString()); 417 continue; 418 case "hashCode": 419 System.out.printf("%s: %d%n", 420 arg, stack[top - 1].hashCode()); 421 continue; 422 case "equals": 423 System.out.printf("%s: %b%n", 424 arg, 425 stack[top - 2].equals(stack[top - 1])); 426 continue; 427 428 case "intValue": 429 System.out.printf("%s: %d%n", 430 arg, stack[top - 1].intValue()); 431 continue; 432 case "longValue": 433 System.out.printf("%s: %d%n", 434 arg, stack[top - 1].longValue()); 435 continue; 436 case "doubleValue": 437 System.out.printf("%s: %.15e%n", 438 arg, stack[top - 1].doubleValue()); 439 continue; 440 case "floatValue": 441 System.out.printf("%s: %.7e%n", 442 arg, stack[top - 1].floatValue()); 443 continue; 444 445 case "compareTo": 446 System.out.printf("%s: %d%n", 447 arg, 448 stack[top - 2].compareTo(stack[top - 1])); 449 continue; 450 451 case "formatTo": 452 System.err.print("Enter format string: "); 453 try { 454 System.out.printf("%s: " + input.next() + "%n", 455 arg, stack[top - 1]); 456 } catch (NoSuchElementException x) { 457 System.err.println("no input"); 458 System.exit(1); 459 } 460 continue; 461 462 case "ZERO": 463 stack[top++] = Rational.ZERO; 464 continue; 465 case "ONE": 466 stack[top++] = Rational.ONE; 467 continue; 468 case "MAX_VALUE": 469 stack[top++] = Rational.MAX_VALUE; 470 continue; 471 case "MIN_VALUE": 472 stack[top++] = Rational.MIN_VALUE; 473 continue; 474 475 case "int": 476 System.err.print("Enter integer: "); 477 try { 478 stack[top] = Rational.valueOf(input.nextInt()); 479 ++top; 480 } catch (NoSuchElementException x) { 481 System.err.println(input.next() + ": invalid input"); 482 System.exit(1); 483 } 484 continue; 485 case "intint": 486 System.err.print("Enter numerator and denominator: "); 487 try { 488 stack[top] = Rational.valueOf(input.nextInt(), 489 input.nextInt()); 490 ++top; 491 } catch (NoSuchElementException x) { 492 System.err.println(input.next() + ": invalid input"); 493 System.exit(1); 494 } 495 continue; 496 default: 497 stack[top] = Rational.valueOf(arg); 498 ++top; 499 continue; 500 } 501 502 System.out.println(arg); 503 504 } catch (ArrayIndexOutOfBoundsException x) { 505 System.err.println(arg + ": missing operand"); 506 System.exit(1); 507 } catch (NumberFormatException x) { 508 System.err.println(x.getMessage() + ": not a rational number"); 509 } catch (ArithmeticException x) { 510 System.err.println(x.getMessage()); 511 } catch (IllegalFormatException x) { 512 System.err.printf("%s: %s%n", arg, x); 513 } catch (IllegalArgumentException x) { 514 System.err.println(x.getMessage()); 515 } 516 } 517 518 for (int j = 0; j < top; ++j) { 519 System.out.printf("%d: %s (%.15e)%n", 520 j, stack[j], stack[j].doubleValue()); 521 } 522 } 523} 524