2 import java.util.Scanner;
3 import java.util.Locale;
4 import java.util.NoSuchElementException;
5 import java.util.Formatter;
6 import java.util.Formattable;
7 import java.util.regex.Pattern;
8 import static java.util.FormattableFlags.ALTERNATE;
9 import static java.util.FormattableFlags.LEFT_JUSTIFY;
10 import java.util.FormatFlagsConversionMismatchException;
11 import java.util.IllegalFormatPrecisionException;
12 import java.util.IllegalFormatException;
13
14 /**
15 * A rational number, stored as numerator and denominator integer pair.
16 * Rational is a value-based class.
17 * @author H.Drachenfels
18 * @version 22.1.2024
19 */
20 public final class Rational extends Number
21 implements Comparable<Rational>, Formattable {
22
23 /**
24 * The number 0 as Rational.
25 */
26 public static final Rational ZERO = new Rational(0, 1);
27
28 /**
29 * The number 1 as Rational.
30 */
31 public static final Rational ONE = new Rational(1, 1);
32
33 /**
34 * The largest positive value of type Rational, Integer.MAX_VALUE.
35 */
36 public static final Rational MAX_VALUE
37 = new Rational(Integer.MAX_VALUE, 1);
38
39 /**
40 * The smallest positive nonzero value of type Rational,
41 * 1 / Integer.MAX_VALUE.
42 */
43 public static final Rational MIN_VALUE
44 = new Rational(1, Integer.MAX_VALUE);
45
46 /**
47 * The numerator part of the rational number.
48 */
49 private final int numerator;
50
51 /**
52 * The denominator part of the rational number.
53 * Always greater or equal 1.
54 * The denominator is 1 if numerator is 0.
55 */
56 private final int denominator;
57
58 /**
59 * Rational number with numerator n and denominator d.
60 * For denominator 0 an IllegalArgumentException is thrown.
61 * @param n the numerator
62 * @param d the denominator (must not be 0)
63 * @return a Rational with the specified value
64 */
65 public static Rational valueOf(int n, int d) {
66 return new Rational(n, d);
67 }
68
69 /**
70 * Converts an integer to a rational number.
71 * This factory method is provided in preference to an (int) constructor
72 * because it allows for reuse of frequently used Rationals.
73 * @param n the integer to convert
74 * @return a Rational with the specified value
75 */
76 public static Rational valueOf(int n) {
77 switch (n) {
78 case 0:
79 return ZERO;
80 case 1:
81 return ONE;
82 case Integer.MAX_VALUE:
83 return MAX_VALUE;
84 default:
85 return new Rational(n, 1);
86 }
87 }
88
89 /**
90 * Converts a string to a rational number.
91 * The string must have the format specified by toString.
92 * Otherwise NumberFormatException is thrown.
93 * @param s the string to convert
94 * @return a Rational with the specified value
95 */
96 public static Rational valueOf(String s) {
97 Scanner sc = new Scanner(s);
98 if (!sc.hasNext(FORMAT)) {
99 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 Euclids 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 decimal representation including sign of the numerator
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 */
354 final 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