diff --git a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/ContinuedFraction.java b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/ContinuedFraction.java index ed8323c13..6feb37f34 100644 --- a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/ContinuedFraction.java +++ b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/ContinuedFraction.java @@ -71,6 +71,10 @@ public ContinuedFraction() {} /** * Evaluates the continued fraction. * + *
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}. diff --git a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java index 0b5a0882b..6e33faef1 100644 --- a/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java +++ b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java @@ -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. + * + *
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. * @@ -149,6 +156,10 @@ private GeneralizedContinuedFraction() {} * *
Note: The first generated partial numerator a0 is discarded. * + *
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
@@ -164,6 +175,10 @@ public static double value(Supplier Note: The first generated partial numerator a0 is discarded.
*
+ * 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.
@@ -227,6 +242,10 @@ public static double value(Supplier 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 b0.
* @param gen Generator of coefficients.
* @return the value of the continued fraction.
@@ -250,6 +269,10 @@ public static double value(double b0, Supplier 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 b0.
* @param gen Generator of coefficients.
* @param epsilon Maximum relative error allowed.
diff --git a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java
index 7eb68237b..256cad386 100644
--- a/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java
+++ b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java
@@ -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 NUMBERS-214
+ */
+ @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 NUMBERS-214
+ */
+ @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