Implementing a comparable numeric data type in Java the TDD way

Image for post
Image for post
A partitioned cake. This looks like 1/2 + 1/3 + 1/6 to me. Photo by Annie Spratt on Unsplash

Making a numeric approximation function

    // STUB TO FAIL THE FIRST TEST
public double getNumericValue() {
return 0.0;
}
    @Test
public void testGetNumericValue() {
Fraction fract = new Fraction(-1, 2);
double expected = -0.5;
double testDelta = 0.00000001;
double actual = fract.getNumericValue();
assertEquals(expected, actual, testDelta);
fract = new Fraction(2, 7);
expected = 0.28571428;
actual = fract.getNumericValue();
assertEquals(expected, actual, testDelta);
}

Making a numeric data type comparable

    // STUB TO FAIL THE FIRST TEST
@Override
public int compareTo(Fraction other) {
return 0;
}
scala> 7.compareTo(6)
res9: Int = 1
scala> 7.compareTo(7)
res10: Int = 0
scala> 7.compareTo(8)
res11: Int = -1
scala> 7.compareTo(null)
java.lang.NullPointerException
at java.lang.Integer.compareTo(Unknown Source)
... 28 elided
scala> 7 == null
<console>:12: warning: comparing values of types Int and Null using `==' will al
ways yield false
7 == null
^
res13: Boolean = false
scala> 7.equals(null)
res14: Boolean = false
    @Test
public void testCompareTo() {
Fraction negThreeHalves = new Fraction(-3, 2);
Fraction approxPiFiftieths = new Fraction(157, 50);
Fraction approxPi113ths = new Fraction(355, 113);
Fraction approxPiSevenths = new Fraction(22, 7);
int comparison =
negThreeHalves.compareTo(approxPiFiftieths);
String assertionMessage = negThreeHalves.toString()
+ " should be found to be less than "
+ approxPiFiftieths.toString();
assertTrue(assertionMessage, comparison < 0);
comparison = approxPiFiftieths.compareTo(approxPi113ths);
assertionMessage = approxPiFiftieths.toString()
+ " should be found to be less than "
+ approxPi113ths.toString();
assertTrue(assertionMessage, comparison < 0);
comparison = approxPi113ths.compareTo(approxPiSevenths);
assertionMessage = approxPi113ths.toString()
+ " should be found to be less than "
+ approxPiSevenths.toString();
assertTrue(assertionMessage, comparison < 0);
comparison = approxPiFiftieths.compareTo(approxPiFiftieths);
assertEquals(0, comparison);
comparison = approxPi113ths.compareTo(approxPi113ths);
assertEquals(0, comparison);
comparison = approxPiSevenths.compareTo(approxPiSevenths);
assertEquals(0, comparison);
comparison = approxPiFiftieths.compareTo(negThreeHalves);
assertionMessage = approxPiFiftieths.toString()
+ " should be found to be greater than "
+ negThreeHalves.toString();
assertTrue(assertionMessage, comparison > 0);
comparison = approxPi113ths.compareTo(approxPiFiftieths);
assertionMessage = approxPi113ths.toString()
+ " should be found to be greater than "
+ approxPiFiftieths.toString();
assertTrue(assertionMessage, comparison > 0);
comparison = approxPiSevenths.compareTo(approxPi113ths);
assertionMessage = approxPiSevenths.toString()
+ " should be found to be greater than "
+ approxPi113ths.toString();
assertTrue(assertionMessage, comparison > 0);
}
junit.framework.AssertionFailedError: -3/2 should be found to be less than 157/50
    @Override
public int compareTo(Fraction other) {
double thisVal = this.getNumericValue();
double otherVal = other.getNumericValue();
if (thisVal < otherVal) {
return -1;
}
if (thisVal > otherVal) {
return 1;
}
return 0;
}
    @Test
public void testCompareToCloseFraction() {
Fraction numberA = new Fraction(1, Long.MAX_VALUE);
Fraction numberB = new Fraction(1, Long.MAX_VALUE - 1);
String assertionMessage = numberA.toString()
+ " should be found to be less than "
+ numberB.toString();
assertTrue(assertionMessage,
numberA.compareTo(numberB) < 0);
assertionMessage = numberB.toString()
+ " should be found to be greater than "
+ numberA.toString();
assertTrue(assertionMessage,
numberB.compareTo(numberA) > 0);
}
1/9223372036854775807 should be found to be less than 1/9223372036854775806
scala> 1.0 / 2147483647.0
res15: Double = 4.656612875245797E-10
scala> 1.0 / 2147483646.0
res16: Double = 4.656612877414201E-10
scala> res15 == res16
res17: Boolean = false
    @Test
public void testSubtract() {
Fraction minuend = new Fraction(1, 3);
Fraction subtrahend = new Fraction(1, 7);
Fraction expected = new Fraction(4, 21);
Fraction actual = minuend.minus(subtrahend);
assertEquals(expected, actual);
}
    public Fraction minus(Fraction subtrahend) {
return this.plus(subtrahend.negate());
}
    @Test
public void testNegate() {
Fraction fract = new Fraction(8, 13);
Fraction expected = new Fraction(-8, 13);
Fraction actual = fract.negate();
assertEquals(expected, actual);
fract = new Fraction(-55, 89);
expected = new Fraction(55, 89);
actual = fract.negate();
assertEquals(expected, actual);
}
    @Override
public int compareTo(Fraction other) {
Fraction diff = this.minus(other);
if (diff.numer < 0) {
return -1;
}
if (diff.numer > 0) {
return 1;
}
// and if diff.numer is 0 then
return 0;
}
scala> var numberA = new fractions.Fraction(1, 2147483647)
numberA: fractions.Fraction = 1/2147483647
scala> var numberB = new fractions.Fraction(1, 2147483646)
numberB: fractions.Fraction = 1/2147483646
scala> numberA minus numberB
res18: fractions.Fraction = -1/4611686011984936962
scala> numberA minus numberA
res19: fractions.Fraction = 0
scala> numberB minus numberA
res20: fractions.Fraction = 1/4611686011984936962
scala> numberA.compareTo(numberB)
res21: Int = -1
scala> numberA.compareTo(numberA)
res22: Int = 0
scala> numberB.compareTo(numberA)
res23: Int = 1
scala> import scala.math.Ordering.Implicits._
import scala.math.Ordering.Implicits._
scala> numberA > numberB
res24: Boolean = false
scala> numberA == numberA
res25: Boolean = true
scala> numberB == numberB
res26: Boolean = true
scala> numberA < numberB
res27: Boolean = true
    @Test
public void testCompareToThroughCollectionSort() {
Fraction negThreeHalves = new Fraction(-3, 2);
Fraction approxPiFiftieths = new Fraction(157, 50);
Fraction approxPi113ths = new Fraction(355, 113);
Fraction approxPiSevenths = new Fraction(22, 7);
List<Fraction> expectedList = new ArrayList<>();
expectedList.add(negThreeHalves);
expectedList.add(approxPiFiftieths);
expectedList.add(approxPi113ths);
expectedList.add(approxPiSevenths);
List<Fraction> unsortedList = new ArrayList<>();
unsortedList.add(approxPi113ths);
unsortedList.add(negThreeHalves);
unsortedList.add(approxPiSevenths);
unsortedList.add(approxPiFiftieths);
Collections.sort(unsortedList);
assertEquals(expectedList, unsortedList);
}
expected:<[-3/2, 157/50, 355/113, 22/7]> but was:<[355/113, -3/2, 22/7, 157/50]>
scala> for (n <- 1 to 32) yield new fractions.Fraction(1, n)
res28: scala.collection.immutable.IndexedSeq[fractions.Fraction] = Vector(1, 1/2, 1/3, 1/4, 1/5, 1/6, 1/7, 1/8, 1/9, 1/10, 1/11, 1/12, 1/13, 1/14, 1/15, 1/16, 1/17, 1/18, 1/19, 1/20, 1/21, 1/22, 1/23, 1/24, 1/25, 1/26, 1/27, 1/28, 1/29, 1/30, 1/31, 1/32)
scala> res28.sorted
res29: scala.collection.immutable.IndexedSeq[fractions.Fraction] = Vector(1/32, 1/31, 1/30, 1/29, 1/28, 1/27, 1/26, 1/25, 1/24, 1/23, 1/22, 1/21, 1/20, 1/19, 1/18, 1/17, 1/16, 1/15, 1/14, 1/13, 1/12, 1/11, 1/10, 1/9, 1/8, 1/7, 1/6, 1/5, 1/4, 1/3, 1/2, 1)
scala> res28.sorted
<console>:13: error: No implicit Ordering defined for fractions.Fraction.
res28.sorted
^
scala> var rnd = new java.util.Random
rnd: java.util.Random = java.util.Random@786a9781
scala> for (n <- 1 to 10) yield new fractions.Fraction(rnd.nextInt(100), rnd.nextInt(100) + 1)
res30: scala.collection.immutable.IndexedSeq[fractions.Fraction] = Vector(61/4, 69/22, 50/73, 31/22, 53/55, 10/3, 34/7, 81/23, 91/37, 88/89)
scala> res30.sorted
res31: scala.collection.immutable.IndexedSeq[fractions.Fraction] = Vector(50/73, 53/55, 88/89, 31/22, 91/37, 69/22, 10/3, 81/23, 34/7, 61/4)
scala> res31.map(_.getNumericApproximation)
res32: scala.collection.immutable.IndexedSeq[Double] = Vector(0.684931506849315, 0.9636363636363636, 0.9887640449438202, 1.4090909090909092, 2.4594594594594597, 3.1363636363636362, 3.3333333333333335, 3.5217391304347827, 4.857142857142857, 15.25)

is a composer and photographer from Detroit, Michigan. He has been working on a Java program to display certain mathematical diagrams.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store