Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ public ContinuedFraction() {}
/**
* Evaluates the continued fraction.
*
* <p>Uses a default limit on the number of iterations. Use
* {@link #evaluate(double,double,int)} to specify an explicit {@code maxIterations}
* for a fraction that requires more terms to converge.
*
* @param x the evaluation point.
* @param epsilon Maximum relative error allowed.
* @return the value of the continued fraction evaluated at {@code x}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,15 @@ public final class GeneralizedContinuedFraction {
* eps * |b_n|, e.g., 1e-50".
*/
static final double SMALL = 1e-50;
/** Default maximum number of iterations. */
static final int DEFAULT_ITERATIONS = Integer.MAX_VALUE;
/**
* Default maximum number of iterations.
*
* <p>This is bounded well below {@link Integer#MAX_VALUE} so that a fraction which
* does not converge fails fast with an exception rather than iterating for an
* excessive length of time. A generator that requires more terms than this to
* converge should use the overloads that accept an explicit {@code maxIterations}.
*/
static final int DEFAULT_ITERATIONS = 1_000_000;
/**
* Minimum relative error epsilon. Equal to 1 - Math.nextDown(1.0), or 2^-53.
*
Expand Down Expand Up @@ -149,6 +156,10 @@ private GeneralizedContinuedFraction() {}
*
* <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
*
* <p>Uses a default limit on the number of iterations. Use
* {@link #value(Supplier,double,int)} to specify an explicit {@code maxIterations}
* for a fraction that requires more terms to converge.
*
* @param gen Generator of coefficients.
* @return the value of the continued fraction.
* @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
Expand All @@ -164,6 +175,10 @@ public static double value(Supplier<Coefficient> gen) {
*
* <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
*
* <p>Uses a default limit on the number of iterations. Use
* {@link #value(Supplier,double,int)} to specify an explicit {@code maxIterations}
* for a fraction that requires more terms to converge.
*
* @param gen Generator of coefficients.
* @param epsilon Maximum relative error allowed.
* @return the value of the continued fraction.
Expand Down Expand Up @@ -227,6 +242,10 @@ public static double value(Supplier<Coefficient> gen, double epsilon, int maxIte
* <li>b<sub>0</sub> is very small and the result is expected to approach zero</li>
* </ul>
*
* <p>Uses a default limit on the number of iterations. Use
* {@link #value(double,Supplier,double,int)} to specify an explicit
* {@code maxIterations} for a fraction that requires more terms to converge.
*
* @param b0 Coefficient b<sub>0</sub>.
* @param gen Generator of coefficients.
* @return the value of the continued fraction.
Expand All @@ -250,6 +269,10 @@ public static double value(double b0, Supplier<Coefficient> gen) {
* <li>b<sub>0</sub> is very small and the result is expected to approach zero</li>
* </ul>
*
* <p>Uses a default limit on the number of iterations. Use
* {@link #value(double,Supplier,double,int)} to specify an explicit
* {@code maxIterations} for a fraction that requires more terms to converge.
*
* @param b0 Coefficient b<sub>0</sub>.
* @param gen Generator of coefficients.
* @param epsilon Maximum relative error allowed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,46 @@ void testMaxIterationsThrowsB() {
assertExceptionMessageContains(t, "max");
}

/**
* Test the default number of iterations is bounded well below
* {@link Integer#MAX_VALUE}. A fraction that never converges must fail fast
* using the default (no {@code maxIterations} argument) evaluation methods
* rather than run for an excessive length of time.
*
* @see <a href="https://issues.apache.org/jira/browse/NUMBERS-214">NUMBERS-214</a>
*/
@Test
void testDefaultIterationsIsBounded() {
Assertions.assertTrue(GeneralizedContinuedFraction.DEFAULT_ITERATIONS < Integer.MAX_VALUE / 100,
() -> "Default iterations not bounded: " + GeneralizedContinuedFraction.DEFAULT_ITERATIONS);
}

/**
* Test that evaluation of a non-converging fraction using the default number of
* iterations does not generate substantially more terms than the documented
* default limit. This bounds the runtime of a call that omits the
* {@code maxIterations} argument.
*
* @see <a href="https://issues.apache.org/jira/browse/NUMBERS-214">NUMBERS-214</a>
*/
@Test
void testNonConvergingFractionUsesDefaultIterationLimit() {
// Oscillating generator that never converges:
// b0 = 1 seeds the evaluation (a0 is discarded); all subsequent terms
// (a=1, b=0) create a non-converging oscillation between two values.
final int[] calls = {0};
final Supplier<Coefficient> gen = () -> {
calls[0]++;
return Coefficient.of(1, calls[0] == 1 ? 1 : 0);
};

final Throwable t = Assertions.assertThrows(ArithmeticException.class,
() -> GeneralizedContinuedFraction.value(gen));
assertExceptionMessageContains(t, "max");
Assertions.assertTrue(calls[0] <= GeneralizedContinuedFraction.DEFAULT_ITERATIONS + 1,
() -> "Unexpected number of generator calls: " + calls[0]);
}

@Test
void testNaNThrowsA() {
// Create a NaN during the iteration
Expand Down