added new constructors for Fractions

JIRA: MATH-181

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/math/trunk@615856 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Luc Maisonobe 2008-01-28 11:57:55 +00:00
parent 87cbe1675e
commit 25bda2581a
2 changed files with 132 additions and 6 deletions

View File

@ -32,10 +32,23 @@ public class Fraction extends Number implements Comparable {
/** A fraction representing "0 / 1". */ /** A fraction representing "0 / 1". */
public static final Fraction ZERO = new Fraction(0, 1); public static final Fraction ZERO = new Fraction(0, 1);
/**
* The maximal number of denominator digits that can be requested for double to fraction
* conversion.
* <p>
* When <code>d</code> digits are requested, an integer threshold is
* initialized with the value 10<sup>d</sup>. Therefore, <code>d</code>
* cannot be larger than this constant. Since the java language uses 32 bits
* signed integers, the value for this constant is 9.
* </p>
*
* @see #Fraction(double,int)
*/
public static final int MAX_DENOMINATOR_DIGITS = 9;
/** Serializable version identifier */ /** Serializable version identifier */
private static final long serialVersionUID = 6222990762865980424L; private static final long serialVersionUID = 5463066929751300926L;
/** The denominator. */ /** The denominator. */
private int denominator; private int denominator;
@ -54,7 +67,7 @@ public class Fraction extends Number implements Comparable {
} }
/** /**
* Create a fraction given the double value. * Create a fraction given the double value and maximum error allowed.
* <p> * <p>
* References: * References:
* <ul> * <ul>
@ -71,6 +84,82 @@ public class Fraction extends Number implements Comparable {
*/ */
public Fraction(double value, double epsilon, int maxIterations) public Fraction(double value, double epsilon, int maxIterations)
throws FractionConversionException throws FractionConversionException
{
this(value, epsilon, Integer.MAX_VALUE, maxIterations);
}
/**
* Convert a number of denominator digits to a denominator max value.
* @param denominatorDigits The maximum number of denominator digits.
* @return the maximal value for denominator
* @throws IllegalArgumentException if more than {@link #MAX_DENOMINATOR_DIGITS}
* are requested
*/
private static int maxDenominator(int denominatorDigits)
throws IllegalArgumentException
{
if (denominatorDigits > MAX_DENOMINATOR_DIGITS) {
throw new IllegalArgumentException("too many digits requested");
}
return (int)Math.pow(10, denominatorDigits);
}
/**
* Create a fraction given the double value and maximum number of
* denominator digits.
* <p>
* References:
* <ul>
* <li><a href="http://mathworld.wolfram.com/ContinuedFraction.html">
* Continued Fraction</a> equations (11) and (22)-(26)</li>
* </ul>
* </p>
* @param value the double value to convert to a fraction.
* @param denominatorDigits The maximum number of denominator digits.
* @throws FractionConversionException if the continued fraction failed to
* converge
* @throws IllegalArgumentException if more than {@link #MAX_DENOMINATOR_DIGITS}
* are requested
*/
public Fraction(double value, int denominatorDigits)
throws FractionConversionException, IllegalArgumentException
{
this(value, 0, maxDenominator(denominatorDigits), 100);
}
/**
* Create a fraction given the double value and either the maximum error
* allowed or the maximum number of denominator digits.
* <p>
*
* NOTE: This constructor is called with EITHER
* - a valid epsilon value and the maxDenominator set to Integer.MAX_VALUE
* (that way the maxDenominator has no effect).
* OR
* - a valid maxDenominator value and the epsilon value set to zero
* (that way epsilon only has effect if there is an exact match before
* the maxDenominator value is reached).
* <p>
*
* It has been done this way so that the same code can be (re)used for both
* scenarios. However this could be confusing to users if it were part of
* the public API and this constructor should therefore remain PRIVATE.
* </p>
*
* See JIRA issue ticket MATH-181 for more details:
*
* https://issues.apache.org/jira/browse/MATH-181
*
* @param value the double value to convert to a fraction.
* @param epsilon maximum error allowed. The resulting fraction is within
* <code>epsilon</code> of <code>value</code>, in absolute terms.
* @param maxDenominator maximum denominator value allowed.
* @param maxIterations maximum number of convergents
* @throws FractionConversionException if the continued fraction failed to
* converge.
*/
private Fraction(double value, double epsilon, int maxDenominator, int maxIterations)
throws FractionConversionException
{ {
double r0 = value; double r0 = value;
int a0 = (int)Math.floor(r0); int a0 = (int)Math.floor(r0);
@ -101,7 +190,7 @@ public class Fraction extends Number implements Comparable {
q2 = (a1 * q1) + q0; q2 = (a1 * q1) + q0;
double convergent = (double)p2 / (double)q2; double convergent = (double)p2 / (double)q2;
if (n < maxIterations && Math.abs(convergent - value) > epsilon) { if (n < maxIterations && Math.abs(convergent - value) > epsilon && q2 < maxDenominator) {
p0 = p1; p0 = p1;
p1 = p2; p1 = p2;
q0 = q1; q0 = q1;
@ -117,8 +206,13 @@ public class Fraction extends Number implements Comparable {
throw new FractionConversionException(value, maxIterations); throw new FractionConversionException(value, maxIterations);
} }
this.numerator = p2; if (q2 < maxDenominator) {
this.denominator = q2; this.numerator = p2;
this.denominator = q2;
} else {
this.numerator = p1;
this.denominator = q1;
}
reduce(); reduce();
} }

View File

@ -121,6 +121,38 @@ public class FractionTest extends TestCase {
assertFraction(10, 11, new Fraction((double)10 / (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, 1));
assertFraction(2, 5, new Fraction(0.4, 2));
assertFraction(2, 5, new Fraction(0.4, 3));
assertFraction(3, 5, new Fraction(0.6152, 1));
assertFraction(8, 13, new Fraction(0.6152, 2));
assertFraction(510, 829, new Fraction(0.6152, 3));
assertFraction(769, 1250, new Fraction(0.6152, 4));
try {
new Fraction(0.6152, 15);
fail("an exception should have been thrown");
} catch (IllegalArgumentException iae) {
// 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() { public void testCompareTo() {
Fraction first = new Fraction(1, 2); Fraction first = new Fraction(1, 2);
Fraction second = new Fraction(1, 3); Fraction second = new Fraction(1, 3);