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