Case Study: The Rational Class
Tags: java, programming, learning, beginners
Published: 2024-06-17 20:20:03

This section shows how to design the **Rational** class for representing and processing rational numbers. A rational number has a numerator and a denominator in the form **a/b**, where **a** is the numerator and **b** the denominator. For example, **1/3**, **3/4**, and **10/4** are rational numbers.

A rational number cannot have a denominator of **0**, but a numerator of **0** is fine. Every integer **i** is equivalent to a rational number **i/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 either the data type **double** or **float**. To obtain the exact result, we must 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 root 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 below illustrates the **Rational** class and its relationship to the **Number** class and the **Comparable** interface.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xz0fytrs7kgth2wssyv2.png)

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**. 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, then divide both the numerator and denominator by this value. You can use the method for computing the GCD of two integers **n** and **d**, as suggested in [here](https://dev.to/paulike/case-studies-on-loops-27l1), 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. The program below is a test program.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lebatlhjdw61kzkqcc71.png)

The **main** method creates two rational numbers, **r1** and **r2** (lines 7–8), and displays the results of **r1 + r2**, **r1 - r2**, **r1 x r2**, and **r1 / r2** (lines 11–14). To perform **r1 + r2**, invoke **r1.add(r2)** to return a new **Rational** object. Similarly, invoke **r1.subtract(r2)** for **r1 - r2**, **r1.multiply(r2)** for **r1 x r2**, and **r1.divide(r2)** for **r1 / r2**.

The **doubleValue()** method displays the double value of **r2** (line 15). 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 the program below.

```
package demo;

public class Rational extends Number implements Comparable<Rational> {
	// Data fields for numerator and denominator
	private long numerator = 0;
	private long denominator = 1;
	
	/** Construct a rational with default properties */
	public Rational() {
		this(0, 1);
	}
	
	/** Construct a rational with specified numerator and denominator */
	public Rational(long numerator, long denominator) {
		long gcd = gcd(numerator, denominator);
		this.numerator = ((denominator > 0) ? 1 : -1) * numerator / gcd;
		this.denominator = Math.abs(denominator) / gcd;
	}
	
	/** Find GCD of two numbers */
	private static long gcd(long n, long d) {
		long n1 = Math.abs(n);
		long n2 = Math.abs(d);
		int gcd = 1;
		
		for(int k = 1; k <= n1 && k <= n2; k++) {
			if(n1 % k == 0 && n2 % k == 0)
				gcd = k;
		}
		
		return gcd;
	}
	
	/** Return numerator */
	public long getNumerator() {
		return numerator;
	}
	
	/** Return denominator */
	public long getDenominator() {
		return denominator;
	}
	
	/** Add a rational number to this rational */
	public Rational add(Rational secondRational) {
		long n = numerator * secondRational.getDenominator() + denominator * secondRational.getNumerator();
		long d = denominator * secondRational.getDenominator();
		return new Rational(n, d);
	}
	
	/** Subtract a rational number to this rational */
	public Rational subtract(Rational secondRational) {
		long n = numerator * secondRational.getDenominator() - denominator * secondRational.getNumerator();
		long d = denominator * secondRational.getDenominator();
		return new Rational(n, d);
	}
	
	/** Multiply a rational number to this rational */
	public Rational multiply(Rational secondRational) {
		long n = numerator * secondRational.getNumerator();
		long d = denominator * secondRational.getDenominator();
		return new Rational(n, d);
	}
	
	/** Divide a rational number to this rational */
	public Rational divide(Rational secondRational) {
		long n = numerator * secondRational.getDenominator();
		long d = denominator * secondRational.numerator;
		return new Rational(n, d);
	}
	
	@Override
	public String toString() {
		if(denominator == 1)
			return numerator + "";
		else
			return numerator + "/" + denominator;
	}
	
	@Override // Override the equals method in the Object class
	public boolean equals(Object other) {
		if((this.subtract((Rational)(other))).getNumerator() == 0)
			return true;
		else
			return false;
	}
	
	@Override // Implement the abstract intValue method in Number
	public int intValue() {
		return (int)doubleValue();
	}
	
	@Override // Implement the abstract floatValue method in Number
	public float floatValue() {
		return (float)doubleValue();
	}
	
	@Override // Implement the abstract doubleValue method in Number
	public double doubleValue() {
		return numerator * 1.0 / denominator;
	}
	
	@Override // Implement the abstract longValue method in Number
	public long longValue() {
		return (long)doubleValue();
	}
	
	@Override // Implement the compareTo method in Comparable
	public int compareTo(Rational o) {
		if(this.subtract(o).getNumerator() > 0)
			return 1;
		else if(this.subtract(o).getNumerator() < 0)
			return -1;
		else
			return 0;
	}
}

```

The rational number is encapsulated in a **Rational** object. Internally, a rational number is represented in its lowest terms (line 15), and the numerator determines its sign (line 16). The denominator is always positive (line 17).

The **gcd** method (lines 21–32 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 22–23 in the **Rational** class) is defined in the **Math** class and 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 45–70).

The methods **toString** and **equals** in the **Object** class are overridden in the **Rational** class (lines 72–86). 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.

The abstract methods **intValue**, **longValue**, **floatValue**, and **doubleValue** in the **Number** class are implemented in the **Rational** class (lines 88–106). These methods return the **int**, **long**, **float**, and **double** value for this rational number.

The **compareTo(Rational other)** method in the **Comparable** interface is implemented in the **Rational** class (lines 108–116) to compare this rational number to the other rational number.

The getter methods for the properties **numerator** and **denominator** are provided in the **Rational** class, but the setter methods are not provided, so, once a **Rational** object is created, its contents cannot be changed. The **Rational** class is immutable. The **String** class and the wrapper classes for primitive type values are also immutable.

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. 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.

The **Rational** class has serious limitations and can easily overflow. For example, the following code will display an incorrect result, because the denominator is too large.

`public class Test {
public static void main(String[] args) {
 Rational r1 = new Rational(1, 123456789);
 Rational r2 = new Rational(1, 123456789);
 Rational r3 = new Rational(1, 123456789);
 System.out.println("r1 * r2 * r3 is " +
 r1.multiply(r2.multiply(r3)));
 }
}`


`r1 * r2 * r3 is -1/2204193661661244627`

To fix it, you can implement the **Rational** class using the **BigInteger** for numerator and denominator.