Improve Fraction Javadoc, implementation and tests

bug 22386, from Phil Steitz


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/lang/trunk@137586 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2003-08-13 23:42:17 +00:00
parent 20c35a1d87
commit 3e2d12ad71
2 changed files with 228 additions and 70 deletions

View File

@ -66,7 +66,7 @@ import java.io.Serializable;
* @author Stephen Colebourne * @author Stephen Colebourne
* @author Tim O'Brien * @author Tim O'Brien
* @since 2.0 * @since 2.0
* @version $Id: Fraction.java,v 1.8 2003/08/04 02:01:53 scolebourne Exp $ * @version $Id: Fraction.java,v 1.9 2003/08/13 23:42:17 scolebourne Exp $
*/ */
public final class Fraction extends Number implements Serializable, Comparable { public final class Fraction extends Number implements Serializable, Comparable {
@ -161,6 +161,8 @@ public final class Fraction extends Number implements Serializable, Comparable {
* @throws ArithmeticException if the denomiator is <code>zero</code> * @throws ArithmeticException if the denomiator is <code>zero</code>
* @throws ArithmeticException if the denomiator is negative * @throws ArithmeticException if the denomiator is negative
* @throws ArithmeticException if the numerator is negative * @throws ArithmeticException if the numerator is negative
* @throws ArithmeticException if the resulting numerator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public static Fraction getFraction(int whole, int numerator, int denominator) { public static Fraction getFraction(int whole, int numerator, int denominator) {
if (denominator == 0) { if (denominator == 0) {
@ -172,12 +174,16 @@ public final class Fraction extends Number implements Serializable, Comparable {
if (numerator < 0) { if (numerator < 0) {
throw new ArithmeticException("The numerator must not be negative"); throw new ArithmeticException("The numerator must not be negative");
} }
double numeratorValue = 0;
if (whole < 0) { if (whole < 0) {
numerator = whole * denominator - numerator; numeratorValue = (double) whole * denominator - numerator;
} else { } else {
numerator = whole * denominator + numerator; numeratorValue = (double) whole * denominator + numerator;
} }
return new Fraction(numerator, denominator); if (Math.abs(numeratorValue) > Integer.MAX_VALUE) {
throw new ArithmeticException("Numerator too large to represent as an Integer.");
}
return new Fraction((int) numeratorValue, denominator);
} }
/** /**
@ -199,30 +205,34 @@ public final class Fraction extends Number implements Serializable, Comparable {
numerator = -numerator; numerator = -numerator;
denominator = -denominator; denominator = -denominator;
} }
int gcd = greatestCommonDenominator(Math.abs(numerator), denominator); int gcd = greatestCommonDivisor(Math.abs(numerator), denominator);
return new Fraction(numerator / gcd, denominator / gcd); return new Fraction(numerator / gcd, denominator / gcd);
} }
/** /**
* <p>Creates a <code>Fraction</code> instance from a <code>double</code> value.</p> * <p>Creates a <code>Fraction</code> instance from a <code>double</code> value.</p>
* *
* <p>This method uses the continued fraction algorithm.</p> * <p>This method uses the <a href="http://archives.math.utk.edu/articles/atuyl/confrac/">
* continued fraction algorithm</a>, computing a maximum of
* 25 convergents and bounding the denominator by 10,000.</p>
* *
* @param value the double value to convert * @param value the double value to convert
* @return a new fraction instance that is close to the value * @return a new fraction instance that is close to the value
* @throws ArithmeticException if the value is infinite or <code>NaN</code> * @throws ArithmeticException if <code>|value| > Integer.MAX_VALUE</code>
* or <code>value = NaN</code>
* @throws ArithmeticException if the calculated denomiator is <code>zero</code> * @throws ArithmeticException if the calculated denomiator is <code>zero</code>
* @throws ArithmeticException if the the algorithm does not converge
*/ */
public static Fraction getFraction(double value) { public static Fraction getFraction(double value) {
if (Double.isInfinite(value) || Double.isNaN(value)) {
throw new ArithmeticException("The value must not be infinite or NaN");
}
int sign = (value < 0 ? -1 : 1); int sign = (value < 0 ? -1 : 1);
value = Math.abs(value); value = Math.abs(value);
if (value > Integer.MAX_VALUE || Double.isNaN(value)) {
throw new ArithmeticException
("The value must not be greater than Integer.MAX_VALUE or NaN");
}
int wholeNumber = (int) value; int wholeNumber = (int) value;
value -= wholeNumber; value -= wholeNumber;
// http://archives.math.utk.edu/articles/atuyl/confrac/
int numer0 = 0; // the pre-previous int numer0 = 0; // the pre-previous
int denom0 = 1; // the pre-previous int denom0 = 1; // the pre-previous
int numer1 = 1; // the previous int numer1 = 1; // the previous
@ -430,7 +440,7 @@ public final class Fraction extends Number implements Serializable, Comparable {
* @return a new reduce fraction instance, or this if no simplification possible * @return a new reduce fraction instance, or this if no simplification possible
*/ */
public Fraction reduce() { public Fraction reduce() {
int gcd = greatestCommonDenominator(Math.abs(numerator), denominator); int gcd = greatestCommonDivisor(Math.abs(numerator), denominator);
return Fraction.getFraction(numerator / gcd, denominator / gcd); return Fraction.getFraction(numerator / gcd, denominator / gcd);
} }
@ -483,27 +493,39 @@ public final class Fraction extends Number implements Serializable, Comparable {
* *
* @param power the power to raise the fraction to * @param power the power to raise the fraction to
* @return <code>this</code> if the power is one, <code>ONE</code> if the power * @return <code>this</code> if the power is one, <code>ONE</code> if the power
* is zero or a new fraction instance raised to the appropriate power * is zero (even if the fraction equals ZERO) or a new fraction instance
* raised to the appropriate power
* @throws ArithmeticException if the resulting numerator or denominator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public Fraction pow(int power) { public Fraction pow(int power) {
if (power == 1) { if (power == 1) {
return this; return this;
} else if (power == 0) { } else if (power == 0) {
return ONE; return ONE;
} else if (power < 0) { } else {
return getFraction((int) Math.pow(denominator, -power), (int) Math.pow(numerator, -power)); double denominatorValue = Math.pow(denominator, power);
double numeratorValue = Math.pow(numerator, power);
if (numeratorValue > Integer.MAX_VALUE || denominatorValue > Integer.MAX_VALUE) {
throw new ArithmeticException("Integer overflow");
}
if (power < 0) {
return getFraction((int) Math.pow(denominator, -power),
(int) Math.pow(numerator, -power));
}
return getFraction((int) Math.pow(numerator, power),
(int) Math.pow(denominator, power));
} }
return getFraction((int) Math.pow(numerator, power), (int) Math.pow(denominator, power));
} }
/** /**
* <p>Gets the greatest common denominator of two numbers.</p> * <p>Gets the greatest common divisor of two numbers.</p>
* *
* @param number1 a positive number * @param number1 a positive number
* @param number2 a positive number * @param number2 a positive number
* @return the greatest common denominator, never zero * @return the greatest common divisor, never zero
*/ */
private static int greatestCommonDenominator(int number1, int number2) { private static int greatestCommonDivisor(int number1, int number2) {
int remainder = number1 % number2; int remainder = number1 % number2;
while (remainder != 0) { while (remainder != 0) {
number1 = number2; number1 = number2;
@ -517,15 +539,14 @@ public final class Fraction extends Number implements Serializable, Comparable {
//------------------------------------------------------------------- //-------------------------------------------------------------------
/** /**
* <p>Adds the value of this fraction to another, returning the result.</p> * <p>Adds the value of this fraction to another, returning the result in
* * reduced form.</p>
* <p>The implementation spots common cases of zero numerators and equal
* denominators. Otherwise, it uses <code>(a/b) + (c/d) = (a*d + b*c) / (b*d)</code>
* and then reduces the result.</p>
* *
* @param fraction the fraction to add, must not be <code>null</code> * @param fraction the fraction to add, must not be <code>null</code>
* @return a <code>Fraction</code> instance with the resulting values * @return a <code>Fraction</code> instance with the resulting values
* @throws IllegalArgumentException if the fraction is <code>null</code> * @throws IllegalArgumentException if the fraction is <code>null</code>
* @throws ArithmeticException if the resulting numerator or denominator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public Fraction add(Fraction fraction) { public Fraction add(Fraction fraction) {
if (fraction == null) { if (fraction == null) {
@ -537,55 +558,45 @@ public final class Fraction extends Number implements Serializable, Comparable {
if (fraction.numerator == 0) { if (fraction.numerator == 0) {
return this; return this;
} }
if (denominator == fraction.denominator) { // Compute lcd explicitly to limit overflow
return getReducedFraction(numerator + fraction.numerator, denominator); int gcd = greatestCommonDivisor(Math.abs(fraction.denominator), Math.abs(denominator));
int thisResidue = denominator/gcd;
int thatResidue = fraction.denominator/gcd;
double denominatorValue = Math.abs((double) gcd * thisResidue * thatResidue);
double numeratorValue = (double) numerator * thatResidue + fraction.numerator * thisResidue;
if (Math.abs(numeratorValue) > Integer.MAX_VALUE ||
Math.abs(denominatorValue) > Integer.MAX_VALUE) {
throw new ArithmeticException("Integer overflow");
} }
return getReducedFraction( return Fraction.getReducedFraction((int) numeratorValue, (int) denominatorValue);
numerator * fraction.denominator + denominator * fraction.numerator,
denominator * fraction.denominator
);
} }
/** /**
* <p>Subtracts the value of another fraction from the value of this one, * <p>Subtracts the value of another fraction from the value of this one,
* returning the result.</p> * returning the result in reduced form.</p>
*
* <p>The implementation spots common cases of zero numerators and equal
* denominators. Otherwise, it uses <code>(a/b) - (c/d) = (a*d - b*c) / (b*d)</code>
* and then reduces the result.</p>
* *
* @param fraction the fraction to subtract, must not be <code>null</code> * @param fraction the fraction to subtract, must not be <code>null</code>
* @return a <code>Fraction</code> instance with the resulting values * @return a <code>Fraction</code> instance with the resulting values
* @throws IllegalArgumentException if the fraction is <code>null</code> * @throws IllegalArgumentException if the fraction is <code>null</code>
* @throws ArithmeticException if the resulting numerator or denominator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public Fraction subtract(Fraction fraction) { public Fraction subtract(Fraction fraction) {
if (fraction == null) { if (fraction == null) {
throw new IllegalArgumentException("The fraction must not be null"); throw new IllegalArgumentException("The fraction must not be null");
} }
if (numerator == 0) { return add(fraction.negate());
return fraction.negate();
}
if (fraction.numerator == 0) {
return this;
}
if (denominator == fraction.denominator) {
return getReducedFraction(numerator - fraction.numerator, denominator);
}
return getReducedFraction(
numerator * fraction.denominator - denominator * fraction.numerator,
denominator * fraction.denominator
);
} }
/** /**
* <p>Multiplies the value of this fraction by another, returning the result.</p> * <p>Multiplies the value of this fraction by another, returning the result
* * in reduced form.</p>
* <p>The implementation uses <code>(a/b)*(c/d) = (a*c)/(b*d)</code>
* and then reduces the result.</p>
* *
* @param fraction the fraction to multipy by, must not be <code>null</code> * @param fraction the fraction to multipy by, must not be <code>null</code>
* @return a <code>Fraction</code> instance with the resulting values * @return a <code>Fraction</code> instance with the resulting values
* @throws IllegalArgumentException if the fraction is <code>null</code> * @throws IllegalArgumentException if the fraction is <code>null</code>
* @throws ArithmeticException if the resulting numerator or denominator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public Fraction multiplyBy(Fraction fraction) { public Fraction multiplyBy(Fraction fraction) {
if (fraction == null) { if (fraction == null) {
@ -594,22 +605,25 @@ public final class Fraction extends Number implements Serializable, Comparable {
if (numerator == 0 || fraction.numerator == 0) { if (numerator == 0 || fraction.numerator == 0) {
return ZERO; return ZERO;
} }
return getReducedFraction( double numeratorValue = (double) numerator * fraction.numerator;
numerator * fraction.numerator, double denominatorValue = (double) denominator * fraction.denominator;
denominator * fraction.denominator if (Math.abs(numeratorValue) > Integer.MAX_VALUE ||
); Math.abs(denominatorValue) > Integer.MAX_VALUE) {
throw new ArithmeticException("Integer overflow");
}
return getReducedFraction((int) numeratorValue, (int) denominatorValue);
} }
/** /**
* <p>Divide the value of this fraction by another, returning the result.</p> * <p>Divide the value of this fraction by another, returning the result
* * in reduced form.</p>
* <p>The implementation uses <code>(a/b)/(c/d) = a/b * d/c = (a*d)/(b*c)</code>
* and then reduces the result.</p>
* *
* @param fraction the fraction to divide by, must not be <code>null</code> * @param fraction the fraction to divide by, must not be <code>null</code>
* @return a <code>Fraction</code> instance with the resulting values * @return a <code>Fraction</code> instance with the resulting values
* @throws IllegalArgumentException if the fraction is <code>null</code> * @throws IllegalArgumentException if the fraction is <code>null</code>
* @throws ArithmeticException if the fraction to divide by is zero * @throws ArithmeticException if the fraction to divide by is zero
* @throws ArithmeticException if the resulting numerator or denominator exceeds
* <code>Integer.MAX_VALUE</code>
*/ */
public Fraction divideBy(Fraction fraction) { public Fraction divideBy(Fraction fraction) {
if (fraction == null) { if (fraction == null) {
@ -621,10 +635,13 @@ public final class Fraction extends Number implements Serializable, Comparable {
if (numerator == 0) { if (numerator == 0) {
return ZERO; return ZERO;
} }
return getReducedFraction( double numeratorValue = (double) numerator * fraction.denominator;
numerator * fraction.denominator, double denominatorValue = (double) denominator * fraction.numerator;
denominator * fraction.numerator if (Math.abs(numeratorValue) > Integer.MAX_VALUE ||
); Math.abs(denominatorValue) > Integer.MAX_VALUE) {
throw new ArithmeticException("Integer overflow");
}
return getReducedFraction((int) numeratorValue, (int) denominatorValue);
} }
// Basics // Basics
@ -668,7 +685,7 @@ public final class Fraction extends Number implements Serializable, Comparable {
* <p>Compares this object to another based on size.</p> * <p>Compares this object to another based on size.</p>
* *
* @param object the object to compare to * @param object the object to compare to
* @return -ve if this is less, 0 if equal, +ve if greater * @return -1 if this is less, 0 if equal, +1 if greater
* @throws ClassCastException if the object is not a <code>Fraction</code> * @throws ClassCastException if the object is not a <code>Fraction</code>
* @throws NullPointerException if the object is <code>null</code> * @throws NullPointerException if the object is <code>null</code>
*/ */

View File

@ -60,11 +60,11 @@ import junit.framework.TestSuite;
* Test cases for the {@link Fraction} classes. * Test cases for the {@link Fraction} classes.
* *
* @author Stephen Colebourne * @author Stephen Colebourne
* @version $Id: FractionTest.java,v 1.3 2003/08/04 02:01:53 scolebourne Exp $ * @version $Id: FractionTest.java,v 1.4 2003/08/13 23:42:17 scolebourne Exp $
*/ */
public class FractionTest extends TestCase { public class FractionTest extends TestCase {
private static final int SKIP = 17; private static final int SKIP = 53;
public FractionTest(String name) { public FractionTest(String name) {
super(name); super(name);
@ -165,14 +165,17 @@ public class FractionTest extends TestCase {
// zero denominator // zero denominator
try { try {
f = Fraction.getFraction(1, 0); f = Fraction.getFraction(1, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(2, 0); f = Fraction.getFraction(2, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(-3, 0); f = Fraction.getFraction(-3, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
} }
@ -200,14 +203,17 @@ public class FractionTest extends TestCase {
// negatives // negatives
try { try {
f = Fraction.getFraction(1, -6, -10); f = Fraction.getFraction(1, -6, -10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(1, -6, -10); f = Fraction.getFraction(1, -6, -10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(1, -6, -10); f = Fraction.getFraction(1, -6, -10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
// negative whole // negative whole
@ -217,27 +223,43 @@ public class FractionTest extends TestCase {
try { try {
f = Fraction.getFraction(-1, -6, 10); f = Fraction.getFraction(-1, -6, 10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(-1, 6, -10); f = Fraction.getFraction(-1, 6, -10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(-1, -6, -10); f = Fraction.getFraction(-1, -6, -10);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
// zero denominator // zero denominator
try { try {
f = Fraction.getFraction(0, 1, 0); f = Fraction.getFraction(0, 1, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(1, 2, 0); f = Fraction.getFraction(1, 2, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(-1, -3, 0); f = Fraction.getFraction(-1, -3, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
try {
f = Fraction.getFraction(Integer.MAX_VALUE, 1, 2);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
try {
f = Fraction.getFraction(-Integer.MAX_VALUE, 1, 2);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
} }
@ -279,14 +301,17 @@ public class FractionTest extends TestCase {
// zero denominator // zero denominator
try { try {
f = Fraction.getReducedFraction(1, 0); f = Fraction.getReducedFraction(1, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getReducedFraction(2, 0); f = Fraction.getReducedFraction(2, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getReducedFraction(-3, 0); f = Fraction.getReducedFraction(-3, 0);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
// reduced // reduced
@ -316,14 +341,22 @@ public class FractionTest extends TestCase {
try { try {
f = Fraction.getFraction(Double.NaN); f = Fraction.getFraction(Double.NaN);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(Double.POSITIVE_INFINITY); f = Fraction.getFraction(Double.POSITIVE_INFINITY);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
try { try {
f = Fraction.getFraction(Double.NEGATIVE_INFINITY); f = Fraction.getFraction(Double.NEGATIVE_INFINITY);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
try {
f = Fraction.getFraction((double) Integer.MAX_VALUE + 1);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
// zero // zero
@ -377,7 +410,7 @@ public class FractionTest extends TestCase {
assertEquals(f2.getDenominator(), f.getDenominator()); assertEquals(f2.getDenominator(), f.getDenominator());
} }
} }
// save time by skipping some tests! // save time by skipping some tests! (
for (int i = 1001; i <= 10000; i+=SKIP) { // denominator for (int i = 1001; i <= 10000; i+=SKIP) { // denominator
for (int j = 1; j <= i; j++) { // numerator for (int j = 1; j <= i; j++) { // numerator
try { try {
@ -396,9 +429,11 @@ public class FractionTest extends TestCase {
public void testFactory_String() { public void testFactory_String() {
try { try {
Fraction.getFraction(null); Fraction.getFraction(null);
fail("expecting ArithmeticException");
} catch (IllegalArgumentException ex) {} } catch (IllegalArgumentException ex) {}
} }
public void testFactory_String_double() { public void testFactory_String_double() {
Fraction f = null; Fraction f = null;
@ -422,6 +457,11 @@ public class FractionTest extends TestCase {
f = Fraction.getFraction("2.3R"); f = Fraction.getFraction("2.3R");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try {
f = Fraction.getFraction("2147483648"); // too big
fail("Expecting NumberFormatException");
} catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("."); f = Fraction.getFraction(".");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
@ -448,26 +488,32 @@ public class FractionTest extends TestCase {
try { try {
f = Fraction.getFraction("2 3"); f = Fraction.getFraction("2 3");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("a 3"); f = Fraction.getFraction("a 3");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("2 b/4"); f = Fraction.getFraction("2 b/4");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("2 "); f = Fraction.getFraction("2 ");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction(" 3"); f = Fraction.getFraction(" 3");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction(" "); f = Fraction.getFraction(" ");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
} }
@ -500,18 +546,22 @@ public class FractionTest extends TestCase {
try { try {
f = Fraction.getFraction("2/d"); f = Fraction.getFraction("2/d");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("2e/3"); f = Fraction.getFraction("2e/3");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("2/"); f = Fraction.getFraction("2/");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
try { try {
f = Fraction.getFraction("/"); f = Fraction.getFraction("/");
fail("expecting NumberFomatException");
} catch (NumberFormatException ex) {} } catch (NumberFormatException ex) {}
} }
@ -625,6 +675,12 @@ public class FractionTest extends TestCase {
f = f.pow(-2); f = f.pow(-2);
assertEquals(25, f.getNumerator()); assertEquals(25, f.getNumerator());
assertEquals(9, f.getDenominator()); assertEquals(9, f.getDenominator());
f = Fraction.getFraction(Integer.MAX_VALUE);
try {
f = f.pow(2);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
} }
public void testAdd() { public void testAdd() {
@ -656,12 +712,24 @@ public class FractionTest extends TestCase {
assertEquals(-1, f.getNumerator()); assertEquals(-1, f.getNumerator());
assertEquals(5, f.getDenominator()); assertEquals(5, f.getDenominator());
f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1);
f2 = Fraction.ONE;
f = f1.add(f2);
assertEquals(Integer.MAX_VALUE, f.getNumerator());
assertEquals(1, f.getDenominator());
f1 = Fraction.getFraction(3, 5); f1 = Fraction.getFraction(3, 5);
f2 = Fraction.getFraction(1, 2); f2 = Fraction.getFraction(1, 2);
f = f1.add(f2); f = f1.add(f2);
assertEquals(11, f.getNumerator()); assertEquals(11, f.getNumerator());
assertEquals(10, f.getDenominator()); assertEquals(10, f.getDenominator());
f1 = Fraction.getFraction(3, 8);
f2 = Fraction.getFraction(1, 6);
f = f1.add(f2);
assertEquals(13, f.getNumerator());
assertEquals(24, f.getDenominator());
f1 = Fraction.getFraction(0, 5); f1 = Fraction.getFraction(0, 5);
f2 = Fraction.getFraction(1, 5); f2 = Fraction.getFraction(1, 5);
f = f1.add(f2); f = f1.add(f2);
@ -671,7 +739,26 @@ public class FractionTest extends TestCase {
try { try {
f.add(null); f.add(null);
fail("expecting IllegalArgumentException");
} catch (IllegalArgumentException ex) {} } catch (IllegalArgumentException ex) {}
f1 = Fraction.getFraction(Integer.MAX_VALUE - 1, 1);
f2 = Fraction.ONE;
f = f1.add(f2);
assertEquals(Integer.MAX_VALUE, f.getNumerator());
assertEquals(1, f.getDenominator());
try {
f = f.add(Fraction.ONE); // should overflow
fail("expecting ArithmeticException but got: " + f.toString());
} catch (ArithmeticException ex) {}
try {
f= Fraction.getFraction(-Integer.MAX_VALUE, 1);
f = f.add(f);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
} }
public void testSubtract() { public void testSubtract() {
@ -728,7 +815,16 @@ public class FractionTest extends TestCase {
try { try {
f.subtract(null); f.subtract(null);
fail("expecting IllegalArgumentException");
} catch (IllegalArgumentException ex) {} } catch (IllegalArgumentException ex) {}
try {
f1 = Fraction.getFraction(1, Integer.MAX_VALUE);
f2 = Fraction.getFraction(1, Integer.MAX_VALUE - 1);
f = f1.subtract(f2);
fail("expecting ArithmeticException"); //should overflow
} catch (ArithmeticException ex) {}
} }
public void testMultiply() { public void testMultiply() {
@ -759,9 +855,28 @@ public class FractionTest extends TestCase {
f = f1.multiplyBy(f2); f = f1.multiplyBy(f2);
assertSame(Fraction.ZERO, f); assertSame(Fraction.ZERO, f);
f1 = Fraction.getFraction(2, 7);
f2 = Fraction.ONE;
f = f1.multiplyBy(f2);
assertEquals(2, f.getNumerator());
assertEquals(7, f.getDenominator());
try { try {
f.multiplyBy(null); f.multiplyBy(null);
fail("expecting IllegalArgumentException");
} catch (IllegalArgumentException ex) {} } catch (IllegalArgumentException ex) {}
try {
f1 = Fraction.getFraction(1, Integer.MAX_VALUE);
f = f1.multiplyBy(f1); // should overflow
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
try {
f1 = Fraction.getFraction(1, -Integer.MAX_VALUE);
f = f1.multiplyBy(f1); // should overflow
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
} }
public void testDivide() { public void testDivide() {
@ -779,6 +894,7 @@ public class FractionTest extends TestCase {
f2 = Fraction.ZERO; f2 = Fraction.ZERO;
try { try {
f = f1.divideBy(f2); f = f1.divideBy(f2);
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {} } catch (ArithmeticException ex) {}
f1 = Fraction.getFraction(0, 5); f1 = Fraction.getFraction(0, 5);
@ -786,9 +902,32 @@ public class FractionTest extends TestCase {
f = f1.divideBy(f2); f = f1.divideBy(f2);
assertSame(Fraction.ZERO, f); assertSame(Fraction.ZERO, f);
f1 = Fraction.getFraction(2, 7);
f2 = Fraction.ONE;
f = f1.divideBy(f2);
assertEquals(2, f.getNumerator());
assertEquals(7, f.getDenominator());
f1 = Fraction.getFraction(1, Integer.MAX_VALUE);
f = f1.divideBy(f1);
assertEquals(1, f.getNumerator());
assertEquals(1, f.getDenominator());
try { try {
f.divideBy(null); f.divideBy(null);
fail("IllegalArgumentException");
} catch (IllegalArgumentException ex) {} } catch (IllegalArgumentException ex) {}
try {
f1 = Fraction.getFraction(1, Integer.MAX_VALUE);
f = f1.divideBy(f1.invert()); // should overflow
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
try {
f1 = Fraction.getFraction(1, -Integer.MAX_VALUE);
f = f1.divideBy(f1.invert()); // should overflow
fail("expecting ArithmeticException");
} catch (ArithmeticException ex) {}
} }
public void testEquals() { public void testEquals() {
@ -834,10 +973,12 @@ public class FractionTest extends TestCase {
try { try {
f1.compareTo(null); f1.compareTo(null);
fail("expecting NullPointerException");
} catch (NullPointerException ex) {} } catch (NullPointerException ex) {}
try { try {
f1.compareTo(new Object()); f1.compareTo(new Object());
fail("expecting ClassCastException");
} catch (ClassCastException ex) {} } catch (ClassCastException ex) {}
f2 = Fraction.getFraction(2, 5); f2 = Fraction.getFraction(2, 5);