(See the next iteration.)
I have this class for representing exact fractions. See what I have:
Fraction.java:
package net.coderodde.math;
import java.util.ArrayList;
import java.util.List;
/**
* This class implements a fraction consisting of a numerator and a denominator.
*
* @author Rodion "rodde" Efremov
* @version 1.6 (Apr 29, 2016)
*/
public class Fraction extends Number {
private final long numerator;
private final long denominator;
public Fraction(final long numerator, final long denominator) {
if (denominator == 0) {
throw new IllegalArgumentException("The denominator is zero.");
}
if (numerator == 0) {
this.numerator = 0;
this.denominator = 1;
} else {
final boolean isPositive = isPositive(numerator, denominator);
final long[] data = eliminateCommonFactors(numerator, denominator);
this.numerator = isPositive ? data[0] : -data[0];
this.denominator = data[1];
}
}
public Fraction plus(final Fraction other) {
return new Fraction(this.numerator * other.denominator +
this.denominator * other.numerator,
this.denominator * other.denominator);
}
public Fraction minus(final Fraction other) {
return new Fraction(this.numerator * other.denominator -
this.denominator * other.numerator,
this.denominator * other.denominator);
}
public Fraction multiply(final Fraction other) {
return new Fraction(this.numerator * other.numerator,
this.denominator * other.denominator);
}
public Fraction divide(final Fraction other) {
return new Fraction(this.numerator * other.denominator,
this.denominator * other.numerator);
}
public Fraction abs() {
return new Fraction(Math.abs(numerator), Math.abs(denominator));
}
public long getNumerator() {
return numerator;
}
public long getDenominator() {
return denominator;
}
public Fraction neg() {
return new Fraction(-numerator, denominator);
}
@Override
public String toString() {
return "" + numerator + "/" + denominator;
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (!getClass().equals(o.getClass())) {
return false;
}
final Fraction other = (Fraction) o;
return getNumerator() == other.getNumerator()
&& getDenominator() == other.getDenominator();
}
@Override
public int intValue() {
return (int)(numerator / denominator);
}
@Override
public long longValue() {
return numerator / denominator;
}
@Override
public float floatValue() {
return ((float) numerator) / denominator;
}
@Override
public double doubleValue() {
return ((double) numerator) / denominator;
}
private boolean isPositive(final long numerator, final long denominator) {
final boolean numeratorIsPositive = numerator > 0L;
final boolean denominatorIsPositive = denominator > 0L;
return (numeratorIsPositive && denominatorIsPositive) ||
(!numeratorIsPositive && !denominatorIsPositive);
}
private long[] eliminateCommonFactors(long numerator,
long denominator) {
numerator = Math.abs(numerator);
denominator = Math.abs(denominator);
if (numerator < denominator) {
final List<Long> numeratorPrimeFactorList = factorize(numerator);
for (final long primeFactor : numeratorPrimeFactorList) {
if (denominator % primeFactor == 0) {
denominator /= primeFactor;
numerator /= primeFactor;
}
}
} else {
final List<Long> denominatorPrimeFactorList =
factorize(denominator);
for (final long primeFactor : denominatorPrimeFactorList) {
if (numerator % primeFactor == 0) {
numerator /= primeFactor;
denominator /= primeFactor;
}
}
}
return new long[]{ numerator, denominator };
}
private static List<Long> factorize(long value) {
final List<Long> factorList = new ArrayList();
while (value % 2L == 0) {
factorList.add(2L);
value /= 2L;
}
for (long prime = 3L;
prime <= (long) Math.sqrt(value);
prime = nextPrime(prime)) {
if (prime * prime > value) {
break;
}
while (value % prime == 0L) {
factorList.add(prime);
value /= prime;
}
}
if (value > 1) {
factorList.add(value);
}
return factorList;
}
private static long nextPrime(final long prime) {
long nextPrimeCandidate = prime + 2L;
while (!isPrime(nextPrimeCandidate)) {
nextPrimeCandidate += 2L;
}
return nextPrimeCandidate;
}
private static boolean isPrime(final long primeNumberCandidate) {
final long upperBound = (long) Math.sqrt(primeNumberCandidate);
for (long i = 3L; i < upperBound; i += 2) {
if (primeNumberCandidate % i == 0L) {
return false;
}
}
return true;
}
}
FractionTest.java:
package net.coderodde.math;
import org.junit.Test;
import static org.junit.Assert.*;
public class FractionTest {
private static float DELTA = 0.001f;
@Test
public void testConstruct() {
assertEquals(new Fraction(5, 3) , new Fraction(35, 21));
assertEquals(new Fraction(5, 3) , new Fraction(-35, -21));
assertEquals(new Fraction(-5, 3), new Fraction(-35, 21));
assertEquals(new Fraction(-5, 3), new Fraction(35, -21));
assertEquals(new Fraction(0, 1) , new Fraction(0, 100));
}
@Test(expected = IllegalArgumentException.class)
public void testThrowsOnZeroDenominator() {
new Fraction(1, 0);
}
@Test
public void testPlus() {
// (7 / 3) + (6 / 5) = (35 / 15) + (18 / 15) = 53 / 15
Fraction a = new Fraction(7, 3);
Fraction b = new Fraction(6, 5);
assertEquals(new Fraction(53, 15), a.plus(b));
assertEquals(new Fraction(53, 15), b.plus(a));
a = new Fraction(-7, 3);
b = new Fraction(6, -5);
assertEquals(new Fraction(-53, 15), a.plus(b));
a = new Fraction(7, 3);
b = new Fraction(6, -5);
// (7 / 3) - (6 / 5) = (35 / 15) - (18 / 15) = 17 / 15
assertEquals(new Fraction(17, 15), a.plus(b));
}
@Test
public void testMinus() {
// (7 / 3) - (6 / 5) = (35 / 15) - (18 / 15) = 17 / 15
Fraction a = new Fraction(7, 3);
Fraction b = new Fraction(6, 5);
assertEquals(new Fraction(17, 15), a.minus(b));
assertEquals(new Fraction(17, -15), b.minus(a));
assertEquals(new Fraction(-17, 15), b.minus(a));
}
@Test
public void testMultiply() {
Fraction a = new Fraction(3, 7);
Fraction b = new Fraction(5, 3);
assertEquals(new Fraction(5, 7), a.multiply(b));
b = new Fraction(-5, 3);
assertEquals(new Fraction(-5, 7), a.multiply(b));
assertEquals(new Fraction(5, -7), a.multiply(b));
}
@Test
public void testDivide() {
// (2/9) / (6/4) = (2/9) * (2/3) = 4 / 27
Fraction a = new Fraction(2, 9);
Fraction b = new Fraction(6, 4);
assertEquals(new Fraction(4, 27), a.divide(b));
assertEquals(new Fraction(-4, -27), a.divide(b));
}
@Test
public void testAbs() {
assertEquals(new Fraction(2, 4), new Fraction( 1, 2).abs());
assertEquals(new Fraction(2, 4), new Fraction(-1, 2).abs());
assertEquals(new Fraction(2, 4), new Fraction( 1, -2).abs());
assertEquals(new Fraction(2, 4), new Fraction(-1, -2).abs());
}
@Test
public void testGetNumerator() {
assertEquals(3, new Fraction(6, 4) .getNumerator());
assertEquals(3, new Fraction(3, 2) .getNumerator());
assertEquals(3, new Fraction(9, 6) .getNumerator());
assertEquals(15, new Fraction(15, 11).getNumerator());
}
@Test
public void testGetDenominator() {
assertEquals(2, new Fraction(6, 4) .getDenominator());
assertEquals(2, new Fraction(3, 2) .getDenominator());
assertEquals(2, new Fraction(9, 6) .getDenominator());
assertEquals(11, new Fraction(15, 11).getDenominator());
}
@Test
public void testToString() {
assertEquals("3/2" , new Fraction(6 , 4) .toString());
assertEquals("3/2" , new Fraction(3 , 2) .toString());
assertEquals("3/2" , new Fraction(9 , 6) .toString());
assertEquals("15/11" , new Fraction(15 , 11) .toString());
assertEquals("-15/11", new Fraction(-15, 11) .toString());
assertEquals("-15/11", new Fraction(15 , -11).toString());
assertEquals("15/11" , new Fraction(-15, -11).toString());
assertEquals("0/1" , new Fraction(0, -123) .toString());
}
@Test
public void testIntValue() {
assertEquals(0, new Fraction(0, 4).intValue());
assertEquals(0, new Fraction(1, 4).intValue());
assertEquals(0, new Fraction(2, 4).intValue());
assertEquals(0, new Fraction(3, 4).intValue());
assertEquals(1, new Fraction(4, 4).intValue());
assertEquals(1, new Fraction(5, 4).intValue());
assertEquals(1, new Fraction(6, 4).intValue());
assertEquals(1, new Fraction(7, 4).intValue());
assertEquals(0, new Fraction(-0, 4).intValue());
assertEquals(0, new Fraction(-1, 4).intValue());
assertEquals(0, new Fraction(-2, 4).intValue());
assertEquals(0, new Fraction(-3, 4).intValue());
assertEquals(-1, new Fraction(-4, 4).intValue());
assertEquals(-1, new Fraction(-5, 4).intValue());
assertEquals(-1, new Fraction(-6, 4).intValue());
assertEquals(-1, new Fraction(-7, 4).intValue());
assertEquals(4, new Fraction(-17, -4).intValue());
}
@Test
public void testLongValue() {
assertEquals(0L, new Fraction(0, 4).longValue());
assertEquals(0L, new Fraction(1, 4).longValue());
assertEquals(0L, new Fraction(2, 4).longValue());
assertEquals(0L, new Fraction(3, 4).longValue());
assertEquals(1L, new Fraction(4, 4).longValue());
assertEquals(1L, new Fraction(5, 4).longValue());
assertEquals(1L, new Fraction(6, 4).longValue());
assertEquals(1L, new Fraction(7, 4).longValue());
assertEquals(0L, new Fraction(-0, 4).longValue());
assertEquals(0L, new Fraction(-1, 4).longValue());
assertEquals(0L, new Fraction(-2, 4).longValue());
assertEquals(0L, new Fraction(-3, 4).longValue());
assertEquals(-1L, new Fraction(-4, 4).longValue());
assertEquals(-1L, new Fraction(-5, 4).longValue());
assertEquals(-1L, new Fraction(-6, 4).longValue());
assertEquals(-1L, new Fraction(-7, 4).longValue());
assertEquals(4L, new Fraction(-17, -4).longValue());
}
@Test
public void testFloatValue() {
assertEquals(0.0f , new Fraction(0, 4).floatValue(), DELTA);
assertEquals(0.25f, new Fraction(1, 4).floatValue(), DELTA);
assertEquals(0.5f , new Fraction(2, 4).floatValue(), DELTA);
assertEquals(0.75f, new Fraction(3, 4).floatValue(), DELTA);
assertEquals(1.0f , new Fraction(4, 4).floatValue(), DELTA);
assertEquals(1.25f, new Fraction(5, 4).floatValue(), DELTA);
assertEquals(1.5f , new Fraction(6, 4).floatValue(), DELTA);
assertEquals(1.75f, new Fraction(7, 4).floatValue(), DELTA);
assertEquals(0.0f , new Fraction(-0, 4).floatValue(), DELTA);
assertEquals(-0.25f, new Fraction(-1, 4).floatValue(), DELTA);
assertEquals(-0.5f , new Fraction(-2, 4).floatValue(), DELTA);
assertEquals(-0.75f, new Fraction(-3, 4).floatValue(), DELTA);
assertEquals(-1.0f , new Fraction(-4, 4).floatValue(), DELTA);
assertEquals(-1.25f, new Fraction(-5, 4).floatValue(), DELTA);
assertEquals(-1.5f , new Fraction(-6, 4).floatValue(), DELTA);
assertEquals(-1.75f, new Fraction(-7, 4).floatValue(), DELTA);
assertEquals(4.25f, new Fraction(-17, -4).floatValue(), DELTA);
}
@Test
public void testDoubleValue() {
assertEquals(0.0 , new Fraction(0, 4).doubleValue(), DELTA);
assertEquals(0.25, new Fraction(1, 4).doubleValue(), DELTA);
assertEquals(0.5 , new Fraction(2, 4).doubleValue(), DELTA);
assertEquals(0.75, new Fraction(3, 4).doubleValue(), DELTA);
assertEquals(1.0 , new Fraction(4, 4).doubleValue(), DELTA);
assertEquals(1.25, new Fraction(5, 4).doubleValue(), DELTA);
assertEquals(1.5 , new Fraction(6, 4).doubleValue(), DELTA);
assertEquals(1.75, new Fraction(7, 4).doubleValue(), DELTA);
assertEquals(0.0 , new Fraction(-0, 4).doubleValue(), DELTA);
assertEquals(-0.25, new Fraction(-1, 4).doubleValue(), DELTA);
assertEquals(-0.5 , new Fraction(-2, 4).doubleValue(), DELTA);
assertEquals(-0.75, new Fraction(-3, 4).doubleValue(), DELTA);
assertEquals(-1.0 , new Fraction(-4, 4).doubleValue(), DELTA);
assertEquals(-1.25, new Fraction(-5, 4).doubleValue(), DELTA);
assertEquals(-1.5 , new Fraction(-6, 4).doubleValue(), DELTA);
assertEquals(-1.75, new Fraction(-7, 4).doubleValue(), DELTA);
assertEquals(4.25, new Fraction(-17, -4).doubleValue(), DELTA);
}
}
Any critique is much appreciated.
final
? I'm not saying it's bad (it certainly doesn't have to be), but I'm unsure why/what are you trying to convey with that... seems awfully like a code smell to me in this case. In Cconst
parameters are actually useful - in Java you're only locking yourself from re-using them inside of the method (which almost nobody does anyway), while conveying virtually nothing about the const-ness of the passed argument itself (since, while the reference isfinal
, the object it's still completely mutable). \$\endgroup\$final
classes (extremely useful and helpful) and againstfinal
arguments in methods (mostly useless and redundant). \$\endgroup\$final
, others against it, so this is rather subjective argument. \$\endgroup\$