A rational number is a number with a numerator and a denominator in the form a/b , where a is the numerator and b is the denominator. For example, 1/3 , 3/4 , and 10/4 are rational numbers .
A rational number cannot have a denominator of , but a numerator of is fine. Every integer a is equivalent to a rational number a/1 . Rational numbers are used in exact computations involving fractions; for example, 1/3 = 0.33333 .... This number cannot be precisely represented in floating-point format using data type double or float . To obtain the exact result, it is necessary to use rational numbers.
Java provides data types for integers and floating-point numbers, but not for rational numbers. This section shows how to design a class to represent rational numbers.
Since rational numbers share many common features with integers and floating-point numbers, and Number is the base class for numeric wrapper classes, it is appropriate to define Rational as a subclass of Number . Since rational numbers are comparable, the Rational class should also implement the Comparable interface. Figure 11.12 illustrates the Rational class and its relationship to the Number class and the Comparable interface.
A rational number consists of a numerator and a denominator. There are many equivalent rational numbers; for example, 1/3 = 2/6 = 3/9 = 4/12. For convenience, 1/3 is used in this example to represent all rational numbers that are equivalent to 1/3. The numerator and the denominator of 1/3 have no common divisor except 1, so 1/3 is said to be in lowest terms.
To reduce a rational number to its lowest terms, you need to find the greatest common divisor (GCD) of the absolute values of its numerator and denominator, and then divide both numerator and denominator by this value. You can use the method for computing the GCD of two integers n and d , as suggested in Listing 4.6, GreatestCommonDivisor.java. The numerator and denominator in a Rational object are reduced to their lowest terms.
As usual, let us first write a test program to create two Rational objects and test its methods. Listing 11.6 is a test program. Its output is shown in Figure 11.13.
1 public class TestRationalClass { 2 /** Main method */ 3 public static void main(String[] args) { 4 // Create and initialize two rational numbers r1 and r2. 5 Rational r1 = new Rational( 4 , 2 ); 6 Rational r2 = new Rational( 2 , 3 ); 7 8 // Display results 9 System.out.println(r1 + " + " + r2 + " = " + r1.add(r2) ); 10 System.out.println(r1 + " - " + r2 + " = " + r1.subtract(r2) ); 11 System.out.println(r1 + " * " + r2 + " = " + r1.multiply(r2) ); 12 System.out.println(r1 + " / " + r2 + " = " + r1.divide(r2) ); 13 System.out.println(r2 + " is " + r2.doubleValue() ); 14 } 15 } |
The main method creates two rational numbers, r1 and r2 (lines 5 “6), and displays the results of r1 + r2 , r1 - r2 , r1 * r2 , and r1 / r2 (lines 9 “12). To perform r1 + r2 , invoke r1.add(r2) to return a new Rational object. Similarly, r1.subtract(r2) is for r1 - r2 , r1.multiply(r2) for r1 * r2 , and r1.divide(r2) for r1 / r2 .
The doubleValue() method displays the double value of r2 (line 13). The doubleValue() method is defined in java.lang.Number and overridden in Rational .
Note that when a string is concatenated with an object using the plus sign ( + ), the object's string representation from the toString() method is used to concatenate with the string. So r1 + " + " + r2 + " = " + r1.add(r2) is equivalent to r1.toString() + " + " + r2.toString() + " = " + r1.add(r2).toString() .
The Rational class is implemented in Listing 11.7.
1 public class Rational extends Number implements Comparable { 2 // Data fields for numerator and denominator 3 private long numerator = ; 4 private long denominator = 1 ; 5 6 /** Construct a rational with default properties */ 7 public Rational() { 8 this ( , 1 ); 9 } 10 11 /** Construct a rational with specified numerator and denominator */ 12 public Rational( long numerator, long denominator) { 13 long gcd = gcd(numerator, denominator); 14 this .numerator = ((denominator > ) ? 1 : -1 ) * numerator / gcd; 15 this .denominator = Math.abs(denominator) / gcd; 16 } 17 18 /** Find GCD of two numbers */ 19 private static long gcd( long n, long d) { 20 long n1 = Math.abs(n); 21 long n2 = Math.abs(d); 22 int gcd = 1 ; 23 24 for ( int k = 1 ; k <= n1 && k <= n2; k++) { 25 if (n1 % k == && n2 % k == ) 26 gcd = k; 27 } 28 29 return gcd; 30 } 31 32 /** Return numerator */ 33 public long getNumerator() { 34 return numerator; 35 } 36 37 /** Return denominator */ 38 public long getDenominator() { 39 return denominator; 40 } 41 42 /** Add a rational number to this rational */ 43 public Rational add(Rational secondRational) { 44 long n = numerator * secondRational.getDenominator() + 45 denominator * secondRational.getNumerator(); 46 long d = denominator * secondRational.getDenominator(); 47 return new Rational(n, d); 48 } 49 50 /** Subtract a rational number from this rational */ 51 public Rational subtract(Rational secondRational) { 52 long n = numerator * secondRational.getDenominator() 53 - denominator * secondRational.getNumerator(); 54 long d = denominator * secondRational.getDenominator(); 55 return new Rational(n, d); 56 } 57 58 /** Multiply a rational number to this rational */ 59 public Rational multiply(Rational secondRational) { 60 long n = numerator * secondRational.getNumerator(); 61 long d = denominator * secondRational.getDenominator(); 62 return new Rational(n, d); 63 } 64 65 /** Divide a rational number from this rational */ 66 public Rational divide(Rational secondRational) { 67 long n = numerator * secondRational.getDenominator(); 68 long d = denominator * secondRational.numerator; 69 return new Rational(n, d); 70 } 71 72 /** Override the toString() method */ 73 public String toString() { 74 if (denominator == 1 ) 75 return numerator + "" ; 76 else 77 return numerator + "/" + denominator; 78 } 79 80 /** Override the equals method in the Object class */ 81 public boolean equals(Object parm1) { 82 if (( this .subtract((Rational)(parm1))).getNumerator() == ) 83 return true ; 84 else 85 return false ; 86 } 87 88 /** Override the hashCode method in the Object class */ 89 public int hashCode() { 90 return new Double( this .doubleValue()).hashCode(); 91 } 92 93 /** Override the abstract intValue method in java.lang.Number */ 94 public int intValue() { 95 return ( int )doubleValue(); 96 } 97 98 /** Override the abstract floatValue method in java.lang.Number */ 99 public float floatValue() { 100 return ( float )doubleValue(); 101 } 102 103 /** Override the doubleValue method in java.lang.Number */ 104 public double doubleValue() { 105 return numerator * 1 . / denominator; 106 } 107 108 /** Override the abstract longValue method in java.lang.Number */ 109 public long longValue() { 110 return ( long )doubleValue(); 111 } 112 113 /** Override the compareTo method in java.lang.Comparable */ 114 public int compareTo(Object o) { 115 if (( this .subtract((Rational)o)).getNumerator() > ) 116 return 1 ; 117 else if (( this .subtract((Rational)o)).getNumerator() < ) 118 return - 1 ; 119 else 120 return ; 121 } 122 } |
The rational number is encapsulated in a Rational object. Internally, a rational number is represented in its lowest terms (line 13), and the numerator determines its sign (line 14). The denominator is always positive (line 15).
The gcd() method (lines 19 “30 in the Rational class) is private; it is not intended for use by clients . The gcd() method is only for internal use by the Rational class. The gcd() method is also static, since it is not dependent on any particular Rational object.
The abs(x) method (lines 20 “21 in the Rational class) is defined in the Math class that returns the absolute value of x .
Two Rational objects can interact with each other to perform add, subtract, multiply, and divide operations. These methods return a new Rational object (lines 43 “70).
The methods toString , equals , and hashCode in the Object class are overridden in the Rational class (lines 73 “91). The toString() method returns a string representation of a Rational object in the form numerator/denominator , or simply numerator if denominator is 1. The equals(Object other) method returns true if this rational number is equal to the other rational number. By contract, if two objects are equal, their hash codes must be the same. For this reason, you should override hashCode whenever the equals method is overridden.
The abstract methods intValue , longValue , floatValue , and doubleValue in the Number class are implemented in the Rational class (lines 94 “111). These methods return int , long , float , and double value for this rational number.
The compareTo(Object other) method in the Comparable interface is implemented in the Rational class (lines 114 “121) to compare this rational number to the other rational number.
Tip
The get methods for the properties numerator and denominator are provided in the Rational class, but the set methods are not provided, so the contents of a Rational object cannot be changed once the object is created. The Rational class is immutable . A well-known example of an immutable class is the String class. The wrapper classes introduced in §10.5, "Processing Primitive Data Type Values as Objects," are also immutable. |
Tip
The numerator and denominator are represented using two variables . It is possible to use an array of two integers to represent the numerator and denominator. See Exercise 11.2. The signatures of the public methods in the Rational class are not changed, although the internal representation of a rational number is changed. This is a good example to illustrate the idea that the data fields of a class should be kept private so as to encapsulate the implementation of the class from the use of the class. |