/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.math.fraction;

import org.apache.commons.math.ConvergenceException;

import junit.framework.TestCase;

/**
 * @version $Revision$ $Date$
 */
public class FractionTest extends TestCase {

	private void assertFraction(int expectedNumerator, int expectedDenominator, Fraction actual) {
		assertEquals(expectedNumerator, actual.getNumerator());
		assertEquals(expectedDenominator, actual.getDenominator());
	}

	public void testConstructor() {
		assertFraction(0, 1, new Fraction(0, 1));
		assertFraction(0, 1, new Fraction(0, 2));
		assertFraction(0, 1, new Fraction(0, -1));
		assertFraction(1, 2, new Fraction(1, 2));
		assertFraction(1, 2, new Fraction(2, 4));
		assertFraction(-1, 2, new Fraction(-1, 2));
		assertFraction(-1, 2, new Fraction(1, -2));
		assertFraction(-1, 2, new Fraction(-2, 4));
		assertFraction(-1, 2, new Fraction(2, -4));

		// overflow
		try {
			new Fraction(Integer.MIN_VALUE, -1);
			fail();
		} catch (ArithmeticException ex) {
			// success
		}
		try {
			new Fraction(1, Integer.MIN_VALUE);
			fail();
		} catch (ArithmeticException ex) {
			// success
		}
		try {
			assertFraction(0, 1, new Fraction(0.00000000000001));
			assertFraction(2, 5, new Fraction(0.40000000000001));
			assertFraction(15, 1, new Fraction(15.0000000000001));

		} catch (ConvergenceException ex) {
			fail(ex.getMessage());
		}
	}

	public void testGoldenRatio() {
		try {
			// the golden ratio is notoriously a difficult number for continuous
			// fraction
			new Fraction((1 + Math.sqrt(5)) / 2, 1.0e-12, 25);
			fail("an exception should have been thrown");
		} catch (ConvergenceException ce) {
			// expected behavior
		} catch (Exception e) {
			fail("wrong exception caught");
		}
	}

	// MATH-179
	public void testDoubleConstructor() throws ConvergenceException {
		assertFraction(1, 2, new Fraction((double) 1 / (double) 2));
		assertFraction(1, 3, new Fraction((double) 1 / (double) 3));
		assertFraction(2, 3, new Fraction((double) 2 / (double) 3));
		assertFraction(1, 4, new Fraction((double) 1 / (double) 4));
		assertFraction(3, 4, new Fraction((double) 3 / (double) 4));
		assertFraction(1, 5, new Fraction((double) 1 / (double) 5));
		assertFraction(2, 5, new Fraction((double) 2 / (double) 5));
		assertFraction(3, 5, new Fraction((double) 3 / (double) 5));
		assertFraction(4, 5, new Fraction((double) 4 / (double) 5));
		assertFraction(1, 6, new Fraction((double) 1 / (double) 6));
		assertFraction(5, 6, new Fraction((double) 5 / (double) 6));
		assertFraction(1, 7, new Fraction((double) 1 / (double) 7));
		assertFraction(2, 7, new Fraction((double) 2 / (double) 7));
		assertFraction(3, 7, new Fraction((double) 3 / (double) 7));
		assertFraction(4, 7, new Fraction((double) 4 / (double) 7));
		assertFraction(5, 7, new Fraction((double) 5 / (double) 7));
		assertFraction(6, 7, new Fraction((double) 6 / (double) 7));
		assertFraction(1, 8, new Fraction((double) 1 / (double) 8));
		assertFraction(3, 8, new Fraction((double) 3 / (double) 8));
		assertFraction(5, 8, new Fraction((double) 5 / (double) 8));
		assertFraction(7, 8, new Fraction((double) 7 / (double) 8));
		assertFraction(1, 9, new Fraction((double) 1 / (double) 9));
		assertFraction(2, 9, new Fraction((double) 2 / (double) 9));
		assertFraction(4, 9, new Fraction((double) 4 / (double) 9));
		assertFraction(5, 9, new Fraction((double) 5 / (double) 9));
		assertFraction(7, 9, new Fraction((double) 7 / (double) 9));
		assertFraction(8, 9, new Fraction((double) 8 / (double) 9));
		assertFraction(1, 10, new Fraction((double) 1 / (double) 10));
		assertFraction(3, 10, new Fraction((double) 3 / (double) 10));
		assertFraction(7, 10, new Fraction((double) 7 / (double) 10));
		assertFraction(9, 10, new Fraction((double) 9 / (double) 10));
		assertFraction(1, 11, new Fraction((double) 1 / (double) 11));
		assertFraction(2, 11, new Fraction((double) 2 / (double) 11));
		assertFraction(3, 11, new Fraction((double) 3 / (double) 11));
		assertFraction(4, 11, new Fraction((double) 4 / (double) 11));
		assertFraction(5, 11, new Fraction((double) 5 / (double) 11));
		assertFraction(6, 11, new Fraction((double) 6 / (double) 11));
		assertFraction(7, 11, new Fraction((double) 7 / (double) 11));
		assertFraction(8, 11, new Fraction((double) 8 / (double) 11));
		assertFraction(9, 11, new Fraction((double) 9 / (double) 11));
		assertFraction(10, 11, new Fraction((double) 10 / (double) 11));
	}

	// MATH-181
	public void testDigitLimitConstructor() throws ConvergenceException {
		assertFraction(2, 5, new Fraction(0.4, 9));
		assertFraction(2, 5, new Fraction(0.4, 99));
		assertFraction(2, 5, new Fraction(0.4, 999));

		assertFraction(3, 5, new Fraction(0.6152, 9));
		assertFraction(8, 13, new Fraction(0.6152, 99));
		assertFraction(510, 829, new Fraction(0.6152, 999));
		assertFraction(769, 1250, new Fraction(0.6152, 9999));
	}

	public void testIntegerOverflow() {
		checkIntegerOverflow(0.75000000001455192);
		checkIntegerOverflow(1.0e10);
	}

	private void checkIntegerOverflow(double a) {
		try {
			new Fraction(a, 1.0e-12, 1000);
			fail("an exception should have been thrown");
		} catch (ConvergenceException ce) {
			// expected behavior
		} catch (Exception e) {
			fail("wrong exception caught");
		}
	}

	public void testEpsilonLimitConstructor() throws ConvergenceException {
		assertFraction(2, 5, new Fraction(0.4, 1.0e-5, 100));

		assertFraction(3, 5, new Fraction(0.6152, 0.02, 100));
		assertFraction(8, 13, new Fraction(0.6152, 1.0e-3, 100));
		assertFraction(251, 408, new Fraction(0.6152, 1.0e-4, 100));
		assertFraction(251, 408, new Fraction(0.6152, 1.0e-5, 100));
		assertFraction(510, 829, new Fraction(0.6152, 1.0e-6, 100));
		assertFraction(769, 1250, new Fraction(0.6152, 1.0e-7, 100));
	}

	public void testCompareTo() {
		Fraction first = new Fraction(1, 2);
		Fraction second = new Fraction(1, 3);
		Fraction third = new Fraction(1, 2);

		assertEquals(0, first.compareTo(first));
		assertEquals(0, first.compareTo(third));
		assertEquals(1, first.compareTo(second));
		assertEquals(-1, second.compareTo(first));

		// these two values are different approximations of PI
		// the first one is approximately PI - 3.07e-18
		// the second one is approximately PI + 1.936e-17
		Fraction pi1 = new Fraction(1068966896, 340262731);
		Fraction pi2 = new Fraction(411557987, 131002976);
		assertEquals(-1, pi1.compareTo(pi2));
		assertEquals(1, pi2.compareTo(pi1));
		assertEquals(0.0, pi1.doubleValue() - pi2.doubleValue(), 1.0e-20);
	}

	public void testDoubleValue() {
		Fraction first = new Fraction(1, 2);
		Fraction second = new Fraction(1, 3);

		assertEquals(0.5, first.doubleValue(), 0.0);
		assertEquals(1.0 / 3.0, second.doubleValue(), 0.0);
	}

	public void testFloatValue() {
		Fraction first = new Fraction(1, 2);
		Fraction second = new Fraction(1, 3);

		assertEquals(0.5f, first.floatValue(), 0.0f);
		assertEquals((float) (1.0 / 3.0), second.floatValue(), 0.0f);
	}

	public void testIntValue() {
		Fraction first = new Fraction(1, 2);
		Fraction second = new Fraction(3, 2);

		assertEquals(0, first.intValue());
		assertEquals(1, second.intValue());
	}

	public void testLongValue() {
		Fraction first = new Fraction(1, 2);
		Fraction second = new Fraction(3, 2);

		assertEquals(0L, first.longValue());
		assertEquals(1L, second.longValue());
	}

	public void testConstructorDouble() {
		try {
			assertFraction(1, 2, new Fraction(0.5));
			assertFraction(1, 3, new Fraction(1.0 / 3.0));
			assertFraction(17, 100, new Fraction(17.0 / 100.0));
			assertFraction(317, 100, new Fraction(317.0 / 100.0));
			assertFraction(-1, 2, new Fraction(-0.5));
			assertFraction(-1, 3, new Fraction(-1.0 / 3.0));
			assertFraction(-17, 100, new Fraction(17.0 / -100.0));
			assertFraction(-317, 100, new Fraction(-317.0 / 100.0));
		} catch (ConvergenceException ex) {
			fail(ex.getMessage());
		}
	}

	public void testAbs() {
		Fraction a = new Fraction(10, 21);
		Fraction b = new Fraction(-10, 21);
		Fraction c = new Fraction(10, -21);

		assertFraction(10, 21, a.abs());
		assertFraction(10, 21, b.abs());
		assertFraction(10, 21, c.abs());
	}

	public void testReciprocal() {
		Fraction f = null;

		f = new Fraction(50, 75);
		f = f.reciprocal();
		assertEquals(3, f.getNumerator());
		assertEquals(2, f.getDenominator());

		f = new Fraction(4, 3);
		f = f.reciprocal();
		assertEquals(3, f.getNumerator());
		assertEquals(4, f.getDenominator());

		f = new Fraction(-15, 47);
		f = f.reciprocal();
		assertEquals(-47, f.getNumerator());
		assertEquals(15, f.getDenominator());

		f = new Fraction(0, 3);
		try {
			f = f.reciprocal();
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		// large values
		f = new Fraction(Integer.MAX_VALUE, 1);
		f = f.reciprocal();
		assertEquals(1, f.getNumerator());
		assertEquals(Integer.MAX_VALUE, f.getDenominator());
	}

	public void testNegate() {
		Fraction f = null;

		f = new Fraction(50, 75);
		f = f.negate();
		assertEquals(-2, f.getNumerator());
		assertEquals(3, f.getDenominator());

		f = new Fraction(-50, 75);
		f = f.negate();
		assertEquals(2, f.getNumerator());
		assertEquals(3, f.getDenominator());

		// large values
		f = new Fraction(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
		f = f.negate();
		assertEquals(Integer.MIN_VALUE + 2, f.getNumerator());
		assertEquals(Integer.MAX_VALUE, f.getDenominator());

		f = new Fraction(Integer.MIN_VALUE, 1);
		try {
			f = f.negate();
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}
	}

	public void testAdd() {
		Fraction a = new Fraction(1, 2);
		Fraction b = new Fraction(2, 3);

		assertFraction(1, 1, a.add(a));
		assertFraction(7, 6, a.add(b));
		assertFraction(7, 6, b.add(a));
		assertFraction(4, 3, b.add(b));

		Fraction f1 = new Fraction(Integer.MAX_VALUE - 1, 1);
		Fraction f2 = Fraction.ONE;
		Fraction f = f1.add(f2);
		assertEquals(Integer.MAX_VALUE, f.getNumerator());
		assertEquals(1, f.getDenominator());
		f = f1.add(1);
		assertEquals(Integer.MAX_VALUE, f.getNumerator());
		assertEquals(1, f.getDenominator());

		f1 = new Fraction(-1, 13 * 13 * 2 * 2);
		f2 = new Fraction(-2, 13 * 17 * 2);
		f = f1.add(f2);
		assertEquals(13 * 13 * 17 * 2 * 2, f.getDenominator());
		assertEquals(-17 - 2 * 13 * 2, f.getNumerator());

		try {
			f.add(null);
			fail("expecting IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
		}

		// if this fraction is added naively, it will overflow.
		// check that it doesn't.
		f1 = new Fraction(1, 32768 * 3);
		f2 = new Fraction(1, 59049);
		f = f1.add(f2);
		assertEquals(52451, f.getNumerator());
		assertEquals(1934917632, f.getDenominator());

		f1 = new Fraction(Integer.MIN_VALUE, 3);
		f2 = new Fraction(1, 3);
		f = f1.add(f2);
		assertEquals(Integer.MIN_VALUE + 1, f.getNumerator());
		assertEquals(3, f.getDenominator());

		f1 = new Fraction(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) {
		}

		// denominator should not be a multiple of 2 or 3 to trigger overflow
		f1 = new Fraction(Integer.MIN_VALUE, 5);
		f2 = new Fraction(-1, 5);
		try {
			f = f1.add(f2); // should overflow
			fail("expecting ArithmeticException but got: " + f.toString());
		} catch (ArithmeticException ex) {
		}

		try {
			f = new Fraction(-Integer.MAX_VALUE, 1);
			f = f.add(f);
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		try {
			f = new Fraction(-Integer.MAX_VALUE, 1);
			f = f.add(f);
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		f1 = new Fraction(3, 327680);
		f2 = new Fraction(2, 59049);
		try {
			f = f1.add(f2); // should overflow
			fail("expecting ArithmeticException but got: " + f.toString());
		} catch (ArithmeticException ex) {
		}
	}

	public void testDivide() {
		Fraction a = new Fraction(1, 2);
		Fraction b = new Fraction(2, 3);

		assertFraction(1, 1, a.divide(a));
		assertFraction(3, 4, a.divide(b));
		assertFraction(4, 3, b.divide(a));
		assertFraction(1, 1, b.divide(b));

		Fraction f1 = new Fraction(3, 5);
		Fraction f2 = Fraction.ZERO;
		try {
			f1.divide(f2);
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		f1 = new Fraction(0, 5);
		f2 = new Fraction(2, 7);
		Fraction f = f1.divide(f2);
		assertSame(Fraction.ZERO, f);

		f1 = new Fraction(2, 7);
		f2 = Fraction.ONE;
		f = f1.divide(f2);
		assertEquals(2, f.getNumerator());
		assertEquals(7, f.getDenominator());

		f1 = new Fraction(1, Integer.MAX_VALUE);
		f = f1.divide(f1);
		assertEquals(1, f.getNumerator());
		assertEquals(1, f.getDenominator());

		f1 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
		f2 = new Fraction(1, Integer.MAX_VALUE);
		f = f1.divide(f2);
		assertEquals(Integer.MIN_VALUE, f.getNumerator());
		assertEquals(1, f.getDenominator());

		try {
			f.divide(null);
			fail("IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
		}

		try {
			f1 = new Fraction(1, Integer.MAX_VALUE);
			f = f1.divide(f1.reciprocal()); // should overflow
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}
		try {
			f1 = new Fraction(1, -Integer.MAX_VALUE);
			f = f1.divide(f1.reciprocal()); // should overflow
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		f1 = new Fraction(6, 35);
		f = f1.divide(15);
		assertEquals(2, f.getNumerator());
		assertEquals(175, f.getDenominator());

	}

	public void testMultiply() {
		Fraction a = new Fraction(1, 2);
		Fraction b = new Fraction(2, 3);

		assertFraction(1, 4, a.multiply(a));
		assertFraction(1, 3, a.multiply(b));
		assertFraction(1, 3, b.multiply(a));
		assertFraction(4, 9, b.multiply(b));

		Fraction f1 = new Fraction(Integer.MAX_VALUE, 1);
		Fraction f2 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
		Fraction f = f1.multiply(f2);
		assertEquals(Integer.MIN_VALUE, f.getNumerator());
		assertEquals(1, f.getDenominator());

		try {
			f.multiply(null);
			fail("expecting IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
		}

		f1 = new Fraction(6, 35);
		f = f1.multiply(15);
		assertEquals(18, f.getNumerator());
		assertEquals(7, f.getDenominator());
	}

	public void testSubtract() {
		Fraction a = new Fraction(1, 2);
		Fraction b = new Fraction(2, 3);

		assertFraction(0, 1, a.subtract(a));
		assertFraction(-1, 6, a.subtract(b));
		assertFraction(1, 6, b.subtract(a));
		assertFraction(0, 1, b.subtract(b));

		Fraction f = new Fraction(1, 1);
		try {
			f.subtract(null);
			fail("expecting IllegalArgumentException");
		} catch (IllegalArgumentException ex) {
		}

		// if this fraction is subtracted naively, it will overflow.
		// check that it doesn't.
		Fraction f1 = new Fraction(1, 32768 * 3);
		Fraction f2 = new Fraction(1, 59049);
		f = f1.subtract(f2);
		assertEquals(-13085, f.getNumerator());
		assertEquals(1934917632, f.getDenominator());

		f1 = new Fraction(Integer.MIN_VALUE, 3);
		f2 = new Fraction(1, 3).negate();
		f = f1.subtract(f2);
		assertEquals(Integer.MIN_VALUE + 1, f.getNumerator());
		assertEquals(3, f.getDenominator());

		f1 = new Fraction(Integer.MAX_VALUE, 1);
		f2 = Fraction.ONE;
		f = f1.subtract(f2);
		assertEquals(Integer.MAX_VALUE - 1, f.getNumerator());
		assertEquals(1, f.getDenominator());
		f = f1.subtract(1);
		assertEquals(Integer.MAX_VALUE - 1, f.getNumerator());
		assertEquals(1, f.getDenominator());

		try {
			f1 = new Fraction(1, Integer.MAX_VALUE);
			f2 = new Fraction(1, Integer.MAX_VALUE - 1);
			f = f1.subtract(f2);
			fail("expecting ArithmeticException"); // should overflow
		} catch (ArithmeticException ex) {
		}

		// denominator should not be a multiple of 2 or 3 to trigger overflow
		f1 = new Fraction(Integer.MIN_VALUE, 5);
		f2 = new Fraction(1, 5);
		try {
			f = f1.subtract(f2); // should overflow
			fail("expecting ArithmeticException but got: " + f.toString());
		} catch (ArithmeticException ex) {
		}

		try {
			f = new Fraction(Integer.MIN_VALUE, 1);
			f = f.subtract(Fraction.ONE);
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		try {
			f = new Fraction(Integer.MAX_VALUE, 1);
			f = f.subtract(Fraction.ONE.negate());
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
		}

		f1 = new Fraction(3, 327680);
		f2 = new Fraction(2, 59049);
		try {
			f = f1.subtract(f2); // should overflow
			fail("expecting ArithmeticException but got: " + f.toString());
		} catch (ArithmeticException ex) {
		}
	}

	public void testEqualsAndHashCode() {
		Fraction zero = new Fraction(0, 1);
		Fraction nullFraction = null;
		assertTrue(zero.equals(zero));
		assertFalse(zero.equals(nullFraction));
		assertFalse(zero.equals(Double.valueOf(0)));
		Fraction zero2 = new Fraction(0, 2);
		assertTrue(zero.equals(zero2));
		assertEquals(zero.hashCode(), zero2.hashCode());
		Fraction one = new Fraction(1, 1);
		assertFalse((one.equals(zero) || zero.equals(one)));
	}

	public void testGetReducedFraction() {
		Fraction threeFourths = new Fraction(3, 4);
		assertTrue(threeFourths.equals(Fraction.getReducedFraction(6, 8)));
		assertTrue(Fraction.ZERO.equals(Fraction.getReducedFraction(0, -1)));
		try {
			Fraction.getReducedFraction(1, 0);
			fail("expecting ArithmeticException");
		} catch (ArithmeticException ex) {
			// expected
		}
		assertEquals(Fraction.getReducedFraction(2, Integer.MIN_VALUE).getNumerator(), -1);
		assertEquals(Fraction.getReducedFraction(1, -1).getNumerator(), -1);
	}

	public void testToString() {
		assertEquals("0", new Fraction(0, 3).toString());
		assertEquals("3", new Fraction(6, 2).toString());
		assertEquals("2 / 3", new Fraction(18, 27).toString());
	}
}
