You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by ah...@apache.org on 2021/11/24 13:05:51 UTC

[commons-numbers] branch master updated (37500b9 -> 4d5258e)

This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git.


    from 37500b9  Change Travis CI openjdk9 to openjdk11
     new 5b87b81  NUMBERS-175: A continued fraction class to compute using a generator
     new 04a08f0  NUMBERS-175: Track changes
     new 41d4a3f  Test isPowerOfTwo with negative arguments and all true powers up to 2^62
     new 4d5258e  Add usable maven modules to the main README.md page

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 README.md                                          |  14 +-
 .../commons/numbers/core/ArithmeticUtilsTest.java  |  19 +-
 .../fraction/GeneralizedContinuedFraction.java     | 460 ++++++++++++++++
 .../fraction/GeneralizedContinuedFractionTest.java | 587 +++++++++++++++++++++
 src/changes/changes.xml                            |   5 +
 5 files changed, 1081 insertions(+), 4 deletions(-)
 create mode 100644 commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java
 create mode 100644 commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java

[commons-numbers] 03/04: Test isPowerOfTwo with negative arguments and all true powers up to 2^62

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 41d4a3fa6d982167b47d62f0f7b2ac2383141dcc
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Nov 24 12:54:45 2021 +0000

    Test isPowerOfTwo with negative arguments and all true powers up to 2^62
---
 .../commons/numbers/core/ArithmeticUtilsTest.java     | 19 +++++++++++++++++--
 src/changes/changes.xml                               |  3 ++-
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/ArithmeticUtilsTest.java b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/ArithmeticUtilsTest.java
index 5f466a2..9652c5b 100644
--- a/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/ArithmeticUtilsTest.java
+++ b/commons-numbers-core/src/test/java/org/apache/commons/numbers/core/ArithmeticUtilsTest.java
@@ -442,6 +442,10 @@ class ArithmeticUtilsTest {
 
     @Test
     void testIsPowerOfTwo() {
+        Assertions.assertFalse(ArithmeticUtils.isPowerOfTwo(-1));
+        Assertions.assertFalse(ArithmeticUtils.isPowerOfTwo(Integer.MIN_VALUE));
+
+        // Small numbers in [0, 1024]
         final int n = 1025;
         final boolean[] expected = new boolean[n];
         Arrays.fill(expected, false);
@@ -449,8 +453,19 @@ class ArithmeticUtilsTest {
             expected[i] = true;
         }
         for (int i = 0; i < expected.length; i++) {
-            final boolean actual = ArithmeticUtils.isPowerOfTwo(i);
-            Assertions.assertEquals(expected[i], actual, Integer.toString(i));
+            final int value = i;
+            final boolean actual = ArithmeticUtils.isPowerOfTwo(value);
+            Assertions.assertEquals(expected[i], actual, () -> Integer.toString(value));
+        }
+
+        // All powers up to 2^62
+        for (int i = 0; i <= 62; i++) {
+            final long value = 1L << i;
+            Assertions.assertTrue(ArithmeticUtils.isPowerOfTwo(value), () -> Long.toString(value));
+            if (value >= 4) {
+                Assertions.assertFalse(ArithmeticUtils.isPowerOfTwo(value + 1), () -> Long.toString(value + 1));
+                Assertions.assertFalse(ArithmeticUtils.isPowerOfTwo(value - 1), () -> Long.toString(value - 1));
+            }
         }
     }
 
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 128245e..d782f18 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -74,7 +74,8 @@ N.B. the Performance testing module requires Java 9+.
 ">
       <action dev="aherbert" type="add" issue="175">
         "GeneralizedContinuedFraction": A continued fraction class to compute using a generator.
-        Allows evaluation of continued fractions from a regular series.
+        Allows evaluation of continued fractions from a regular series where coefficients can
+        be computed iteratively from the previous coefficients.
       </action>
       <action dev="aherbert" type="fix" issue="173">
         "ContinuedFraction": Set a minimum bound on the relative error epsilon. Prevents

[commons-numbers] 02/04: NUMBERS-175: Track changes

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 04a08f07e429a2f0cd1235bfdcda6a2a72bc948f
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Nov 24 12:56:54 2021 +0000

    NUMBERS-175: Track changes
---
 src/changes/changes.xml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index d3baf40..128245e 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -72,6 +72,10 @@ N.B. the Performance testing module requires Java 9+.
 (The unit tests require Java 8+)
 
 ">
+      <action dev="aherbert" type="add" issue="175">
+        "GeneralizedContinuedFraction": A continued fraction class to compute using a generator.
+        Allows evaluation of continued fractions from a regular series.
+      </action>
       <action dev="aherbert" type="fix" issue="173">
         "ContinuedFraction": Set a minimum bound on the relative error epsilon. Prevents
         an infinite loop when the epsilon is zero.

[commons-numbers] 01/04: NUMBERS-175: A continued fraction class to compute using a generator

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 5b87b81f5889d7f4b7f59eb5c9608a66fe26ddef
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Nov 17 18:27:00 2021 +0000

    NUMBERS-175: A continued fraction class to compute using a generator
    
    Added a GeneralizedContinuedFraction class. This has evaluation methods
    to include or exclude the leading term b0.
---
 .../fraction/GeneralizedContinuedFraction.java     | 460 ++++++++++++++++
 .../fraction/GeneralizedContinuedFractionTest.java | 587 +++++++++++++++++++++
 2 files changed, 1047 insertions(+)

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
new file mode 100644
index 0000000..3619240
--- /dev/null
+++ b/commons-numbers-fraction/src/main/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFraction.java
@@ -0,0 +1,460 @@
+/*
+ * 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.numbers.fraction;
+
+import java.util.function.Supplier;
+
+/**
+ * Provides a means to evaluate
+ * <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">generalized continued fractions</a>.
+ *
+ * <p>The continued fraction uses the following form for the numerator ({@code a}) and
+ * denominator ({@code b}) coefficients:
+ * <pre>
+ *              a1
+ * b0 + ------------------
+ *      b1 +      a2
+ *           -------------
+ *           b2 +    a3
+ *                --------
+ *                b3 + ...
+ * </pre>
+ *
+ * <p>A generator of the coefficients must be provided to evaluate the continued fraction.
+ *
+ * <p>The implementation of the fraction evaluation is based on the modified Lentz algorithm
+ * as described on page 508 in:
+ *
+ * <ul>
+ *   <li>
+ *   I. J. Thompson,  A. R. Barnett (1986).
+ *   "Coulomb and Bessel Functions of Complex Arguments and Order."
+ *   Journal of Computational Physics 64, 490-509.
+ *   <a target="_blank" href="https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf">
+ *   https://www.fresco.org.uk/papers/Thompson-JCP64p490.pdf</a>
+ *   </li>
+ * </ul>
+ *
+ * @see <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">Wikipedia: Generalized continued fraction</a>
+ * @see <a href="https://en.wikipedia.org/wiki/Generalized_continued_fraction">MathWorld: Generalized continued fraction</a>
+ */
+public final class GeneralizedContinuedFraction {
+    /**
+     * The value for any number close to zero.
+     *
+     * <p>"The parameter small should be some non-zero number less than typical values of
+     * eps * |b_n|, e.g., 1e-50".
+     */
+    private static final double SMALL = 1e-50;
+    /**
+     * Minimum relative error epsilon. Equal to 1 - Math.nextDown(1.0), or 2^-53.
+     *
+     * <p>The epsilon is used to compare the change in the magnitude of the fraction
+     * convergent to 1.0. In theory eps can be 2^-53 reflecting the smallest reduction in
+     * magnitude possible i.e. {@code next = previous * Math.nextDown(1.0)}, or zero
+     * reflecting exact convergence.
+     *
+     * <p>If set to zero then the algorithm requires exact convergence which may not be possible
+     * due to floating point error in the algorithm. For example the golden ratio will not
+     * converge.
+     *
+     * <p>The minimum value will stop the recursive evaluation at the smallest possible
+     * increase or decrease in the convergent.
+     */
+    private static final double MIN_EPSILON = 0x1.0p-53;
+    /** Maximum relative error epsilon. This is configured to prevent incorrect usage. Values
+     * higher than 1.0 invalidate the relative error lower bound of {@code (1 - eps) / 1}.
+     * Set to 0.5 which is a very weak relative error tolerance. */
+    private static final double MAX_EPSILON = 0.5;
+    /** Default low threshold for change in magnitude. Precomputed using MIN_EPSILON.
+     * Equal to 1 - 2^-53. */
+    private static final double DEFAULT_LOW = 1 - MIN_EPSILON;
+    /** Default absolute difference threshold for change in magnitude. Precomputed using MIN_EPSILON.
+     * Equal to {@code 1 / (1 - 2^-53) = 2^-52}. */
+    private static final double DEFAULT_EPS = 0x1.0p-52;
+    /** Default maximum number of iterations. */
+    private static final int DEFAULT_ITERATIONS = Integer.MAX_VALUE;
+
+    /**
+     * Defines the <a href="https://mathworld.wolfram.com/GeneralizedContinuedFraction.html">
+     * {@code n}-th "a" and "b" coefficients</a> of the continued fraction.
+     */
+    public static final class Coefficient {
+        /** "a" coefficient. */
+        private final double a;
+        /** "b" coefficient. */
+        private final double b;
+
+        /**
+         * @param a "a" coefficient
+         * @param b "b" coefficient
+         */
+        private Coefficient(double a, double b) {
+            this.a = a;
+            this.b = b;
+        }
+
+        /**
+         * Returns the {@code n}-th "a" coefficient of the continued fraction.
+         *
+         * @return the coefficient <code>a<sub>n</sub></code>.
+         */
+        public double getA() {
+            return a;
+        }
+
+        /**
+         * Returns the {@code n}-th "b" coefficient of the continued fraction.
+         *
+         * @return the coefficient <code>b<sub>n</sub></code>.
+         */
+        public double getB() {
+            return b;
+        }
+
+        /**
+         * Create a new coefficient.
+         *
+         * @param a "a" coefficient
+         * @param b "b" coefficient
+         * @return the coefficient
+         */
+        public static Coefficient of(double a, double b) {
+            return new Coefficient(a, b);
+        }
+    }
+
+    /** No instances. */
+    private GeneralizedContinuedFraction() {}
+
+    /**
+     * Evaluates the continued fraction.
+     *
+     * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
+     *
+     * @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
+     * iterations is reached before the expected convergence is achieved.
+     * @see #value(Supplier,double,int)
+     */
+    public static double value(Supplier<Coefficient> gen) {
+        return value(gen, MIN_EPSILON, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Evaluates the continued fraction.
+     *
+     * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
+     *
+     * @param gen Generator of coefficients.
+     * @param epsilon Maximum relative error allowed.
+     * @return the value of the continued fraction.
+     * @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
+     * iterations is reached before the expected convergence is achieved.
+     * @see #value(Supplier,double,int)
+     */
+    public static double value(Supplier<Coefficient> gen, double epsilon) {
+        return value(gen, epsilon, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Evaluates the continued fraction.
+     * <pre>
+     *              a1
+     * b0 + ------------------
+     *      b1 +      a2
+     *           -------------
+     *           b2 +    a3
+     *                --------
+     *                b3 + ...
+     * </pre>
+     *
+     * <p>Setting coefficient a<sub>n</sub> to zero will signal the end of the recursive evaluation.
+     *
+     * <p>Note: The first generated partial numerator a<sub>0</sub> is discarded.
+     *
+     * <h2>Usage Note</h2>
+     *
+     * <p>This method is not functionally identical to calling
+     * {@link #value(double, Supplier, double, int)} with the generator configured to
+     * provide coefficients from n=1 and supplying b<sub>0</sub> separately. In some cases
+     * the computed result from the two variations may be different by more than the
+     * provided epsilon. The other method should be used if b<sub>0</sub> is zero or very
+     * small. See the corresponding javadoc for details.
+     *
+     * @param gen Generator of coefficients.
+     * @param epsilon Maximum relative error allowed.
+     * @param maxIterations Maximum number of iterations.
+     * @return the value of the continued fraction.
+     * @throws ArithmeticException if the algorithm fails to converge or if the maximal number of
+     * iterations is reached before the expected convergence is achieved.
+     * @see #value(double, Supplier, double, int)
+     */
+    public static double value(Supplier<Coefficient> gen, double epsilon, int maxIterations) {
+        // Use the first b coefficient to seed the evaluation of the fraction.
+        // Coefficient a is discarded.
+        final Coefficient c = gen.get();
+        return evaluate(c.getB(), gen, epsilon, maxIterations);
+    }
+
+    /**
+     * Evaluates the continued fraction.
+     *
+     * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
+     * Both of the first generated terms a and b are used. This fraction evaluation
+     * can be used when:
+     * <ul>
+     *  <li>b<sub>0</sub> is not part of a regular series
+     *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
+     *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
+     * </ul>
+     *
+     * @param b0 Coefficient b<sub>0</sub>.
+     * @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 iterations is reached before the expected convergence is achieved.
+     * @see #value(double,Supplier,double,int)
+     */
+    public static double value(double b0, Supplier<Coefficient> gen) {
+        return value(b0, gen, MIN_EPSILON, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Evaluates the continued fraction.
+     *
+     * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
+     * Both of the first generated terms a and b are used. This fraction evaluation
+     * can be used when:
+     * <ul>
+     *  <li>b<sub>0</sub> is not part of a regular series
+     *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
+     *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
+     * </ul>
+     *
+     * @param b0 Coefficient b<sub>0</sub>.
+     * @param gen Generator of coefficients.
+     * @param epsilon Maximum relative error allowed.
+     * @return the value of the continued fraction.
+     * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
+     * of iterations is reached before the expected convergence is achieved.
+     * @see #value(double,Supplier,double,int)
+     */
+    public static double value(double b0, Supplier<Coefficient> gen, double epsilon) {
+        return value(b0, gen, epsilon, DEFAULT_ITERATIONS);
+    }
+
+    /**
+     * Evaluates the continued fraction.
+     * <pre>
+     *              a1
+     * b0 + ------------------
+     *      b1 +      a2
+     *           -------------
+     *           b2 +    a3
+     *                --------
+     *                b3 + ...
+     * </pre>
+     *
+     * <p>Setting coefficient a<sub>n</sub> to zero will signal the end of the recursive evaluation.
+     *
+     * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
+     * Both of the first generated terms a and b are used. This fraction evaluation
+     * can be used when:
+     * <ul>
+     *  <li>b<sub>0</sub> is not part of a regular series
+     *  <li>b<sub>0</sub> is zero and the result will evaluate only the continued fraction component
+     *  <li>b<sub>0</sub> is very small and the result is expected to approach zero
+     * </ul>
+     *
+     * <h2>Usage Note</h2>
+     *
+     * <p>This method is not functionally identical to calling
+     * {@link #value(Supplier, double, int)} with the generator configured to provide term
+     * "b<sub>0</sub>" in the first coefficient. In some cases the computed result from
+     * the two variations may be different by more than the provided epsilon. The
+     * convergence of the continued fraction algorithm relies on computing an update
+     * multiplier applied to the current value. Convergence is faster if the initial value
+     * is close to the final value. The {@link #value(Supplier, double, int)} method will
+     * initialise the current value using b<sub>0</sub> and evaluate the continued
+     * fraction using updates computed from the generated coefficients. This method
+     * initialises the algorithm using b1 to evaluate part of the continued fraction and
+     * computes the result as:
+     *
+     * <pre>
+     *        a1
+     * b0 + ------
+     *       part
+     * </pre>
+     *
+     * <p>This is preferred if b<sub>0</sub> is smaller in magnitude than the continued
+     * fraction component. In particular the evaluation algorithm sets a bound on the
+     * minimum initial value as {@code 1e-50}. If b<sub>0</sub> is smaller than this value
+     * then using this method is the preferred evaluation.
+     *
+     * @param b0 Coefficient b<sub>0</sub>.
+     * @param gen Generator of coefficients.
+     * @param epsilon Maximum relative error allowed.
+     * @param maxIterations Maximum number of iterations.
+     * @return the value of the continued fraction.
+     * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
+     * of iterations is reached before the expected convergence is achieved.
+     * @see #value(Supplier,double,int)
+     */
+    public static double value(double b0, Supplier<Coefficient> gen, double epsilon, int maxIterations) {
+        // Use the first b coefficient to seed the evaluation of the fraction.
+        // Coefficient a is used to compute the final result as the numerator term a1.
+        // The supplied b0 is added to the result.
+        final Coefficient c = gen.get();
+        return b0 + c.getA() / evaluate(c.getB(), gen, epsilon, maxIterations);
+    }
+
+    /**
+     * Evaluates the continued fraction using the modified Lentz algorithm described in
+     * Thompson and Barnett (1986) Journal of Computational Physics 64, 490-509.
+     * <pre>
+     *              a1
+     * b0 + ------------------
+     *      b1 +      a2
+     *           -------------
+     *           b2 +    a3
+     *                --------
+     *                b3 + ...
+     * </pre>
+     *
+     * <p>Note: The initial term b<sub>0</sub> is supplied as an argument.
+     * Both of the first generated terms a and b are used.
+     *
+     * <h2>Implementation Note</h2>
+     *
+     * <p>This method is private and functionally different from
+     * {@link #value(double, Supplier, double, int)}. The convergence of the algorithm relies on
+     * computing an update multiplier applied to the current value, initialised as b0. Accuracy
+     * of the evaluation can be effected if the magnitude of b0 is very different from later
+     * terms. In particular if initialised as 0 the algorithm will not function and so must
+     * set b0 to a small non-zero number. The public methods with the leading b0 term
+     * provide evaluation of the fraction if the term b0 is zero.
+     *
+     * @param b0 Coefficient b<sub>0</sub>.
+     * @param gen Generator of coefficients.
+     * @param epsilon Maximum relative error allowed.
+     * @param maxIterations Maximum number of iterations.
+     * @return the value of the continued fraction.
+     * @throws ArithmeticException if the algorithm fails to converge or if the maximal number
+     * of iterations is reached before the expected convergence is achieved.
+     */
+    private static double evaluate(double b0, Supplier<Coefficient> gen, double epsilon, int maxIterations) {
+        // Relative error epsilon should not be zero to prevent drift in the event
+        // that the update ratio never achieves 1.0.
+
+        // Epsilon is the relative change allowed from 1. Configure the absolute limits so
+        // convergence requires: low <= deltaN <= high
+        // low = 1 - eps
+        // high = 1 / (1 - eps)
+        // High is always further from 1 than low in absolute distance. Do not store high
+        // but store the maximum absolute deviation from 1 for convergence = high - 1.
+        // If this is achieved a second check is made against low.
+        double low;
+        double eps;
+        if (epsilon > MIN_EPSILON && epsilon <= MAX_EPSILON) {
+            low = 1 - epsilon;
+            eps = 1 / low - 1;
+        } else {
+            // Precomputed defaults. Used when epsilon <= MIN_EPSILON
+            low = DEFAULT_LOW;
+            eps = DEFAULT_EPS;
+        }
+
+        double hPrev = updateIfCloseToZero(b0);
+
+        // Notes from Thompson and Barnett:
+        //
+        // Fraction convergent: hn = An / Bn
+        // A(-1) = 1, A0 = b0, B(-1) = 0, B0 = 1
+
+        // Compute the ratios:
+        // Dn = B(n-1) / Bn  = 1 / (an * D(n-1) + bn)
+        // Cn = An / A(n-1)  = an / C(n-1) + bn
+        //
+        // Ratio of successive convergents:
+        // delta n = hn / h(n-1)
+        //         = Cn / Dn
+
+        // Avoid divisors being zero (less than machine precision) by shifting them to e.g. 1e-50.
+
+        double dPrev = 0.0;
+        double cPrev = hPrev;
+
+        for (int n = maxIterations; n > 0; n--) {
+            final Coefficient c = gen.get();
+            final double a = c.getA();
+            final double b = c.getB();
+
+            double dN = updateIfCloseToZero(b + a * dPrev);
+            final double cN = updateIfCloseToZero(b + a / cPrev);
+
+            dN = 1 / dN;
+            final double deltaN = cN * dN;
+            final double hN = hPrev * deltaN;
+
+            // If the fraction is convergent then deltaN -> 1.
+            // Computation of deltaN = 0 or deltaN = big will result in zero or overflow.
+            // Directly check for overflow on hN (this ensures the result is finite).
+
+            if (!Double.isFinite(hN)) {
+                throw new FractionException("Continued fraction diverged to " + hN);
+            }
+
+            // Check for underflow on deltaN. This allows fractions to compute zero
+            // if this is the convergent limit.
+            // Note: deltaN is only zero if dN > 1e-50 / min_value, or 2.02e273.
+            // Since dN is the ratio of convergent denominators this magnitude of
+            // ratio is a presumed to be an error.
+            if (deltaN == 0) {
+                throw new FractionException("Ratio of successive convergents is zero");
+            }
+
+            // Update from Thompson and Barnett to use <= eps in place of < eps.
+            // eps = high - 1
+            // A second check is made to ensure:
+            // low <= deltaN <= high
+            if (Math.abs(deltaN - 1) <= eps && deltaN >= low) {
+                return hN;
+            }
+
+            dPrev = dN;
+            cPrev = cN;
+            hPrev = hN;
+        }
+
+        throw new FractionException("Maximum iterations (%d) exceeded", maxIterations);
+    }
+
+    /**
+     * Returns the value, or if close to zero returns a small epsilon of the same sign.
+     *
+     * <p>This method is used in Thompson & Barnett to monitor both the numerator and denominator
+     * ratios for approaches to zero.
+     *
+     * @param value the value
+     * @return the value (or small epsilon)
+     */
+    private static double updateIfCloseToZero(double value) {
+        return Math.abs(value) < SMALL ? Math.copySign(SMALL, value) : value;
+    }
+}
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
new file mode 100644
index 0000000..dd5fd5a
--- /dev/null
+++ b/commons-numbers-fraction/src/test/java/org/apache/commons/numbers/fraction/GeneralizedContinuedFractionTest.java
@@ -0,0 +1,587 @@
+/*
+ * 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.numbers.fraction;
+
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.function.Supplier;
+import org.apache.commons.numbers.fraction.GeneralizedContinuedFraction.Coefficient;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+/**
+ * Tests for {@link GeneralizedContinuedFraction}.
+ */
+class GeneralizedContinuedFractionTest {
+    /** Golden ratio constant. */
+    private static final double GOLDEN_RATIO = 1.618033988749894848204586834365638117720309;
+    /** Reciprocal of the golden ratio constant. */
+    private static final double INV_GOLDEN_RATIO = 0.618033988749894848204586834365638117720309;
+
+    @Test
+    void testRelativeErrorAssumptions() {
+        Assertions.assertEquals(0x1.0p-52, 1 / (1.0 - 0x1.0p-53) - 1, "Default absolute threshold");
+        for (double eps = 0x1.0p-53; eps <= 0.5; eps *= 2) {
+            final double low = 1 - eps;
+            final double high = 1 / low;
+            Assertions.assertTrue(high - 1.0 >= eps);
+        }
+    }
+
+    @ParameterizedTest
+    @ValueSource(doubles = {1e-6, 1e-10})
+    void testGoldenRatioA(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(GoldenRatio.getInstance(), eps);
+        Assertions.assertEquals(GOLDEN_RATIO, actual, GOLDEN_RATIO * eps);
+    }
+
+    /**
+     * Test a continued fraction where the leading term is added after evaluation.
+     * Evaluates the golden ratio.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {1e-6, 1e-10})
+    void testGoldenRatioB(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(1, GoldenRatio.getInstance(), eps);
+        Assertions.assertEquals(GOLDEN_RATIO, actual, GOLDEN_RATIO * eps);
+    }
+
+    /**
+     * Test a continued fraction where the leading term is zero.
+     * Evaluates the reciprocal of the golden ratio.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {1e-6, 1e-10})
+    void testGoldenRatioC(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(0, GoldenRatio.getInstance(), eps);
+        Assertions.assertEquals(INV_GOLDEN_RATIO, actual, INV_GOLDEN_RATIO * eps);
+    }
+
+    /**
+     * Test a continued fraction where the leading term is not part of a series.
+     * Evaluates the base of the natural logarithm.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {1e-6, 1e-10})
+    void testBaseE(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(2, new E(), eps);
+        Assertions.assertEquals(Math.E, actual, Math.E * eps);
+    }
+
+    /**
+     * Test a continued fraction where the leading term is zero.
+     * Evaluates tan(x) which approaches zero when {@code x -> 0}.
+     */
+    @ParameterizedTest
+    @CsvSource(value = {
+        "0.5, 1e-6",
+        "1.0, 1e-6",
+        "2.0, 1e-6",
+        "0.5, 1e-10",
+        "1.0, 1e-10",
+        "2.0, 1e-10",
+        // tan(x) -> 0 as x -> 0
+        "1e-5, 1e-10",
+        "1e-10, 1e-10",
+        "1e-100, 1e-10",
+        "1e-200, 1e-10",
+    })
+    void testTanX(double x, double eps) {
+        final double expected = Math.tan(x);
+        final double actual = GeneralizedContinuedFraction.value(0, new Tan(x), eps);
+        Assertions.assertEquals(expected, actual, Math.abs(expected) * eps);
+    }
+
+    /**
+     * Test a continued fraction where the leading term is zero.
+     * Evaluates tan(x) which approaches zero when {@code x -> 0}.
+     * This uses a different formulation to {@link #testTanX(double, double)} which
+     * is more accurate when {@code x < 1}. This test demonstrates that the optimum
+     * evaluation requires testing of different implementations of the continued fraction.
+     */
+    @ParameterizedTest
+    @CsvSource(value = {
+        // Cases known to be more accurate when the fraction is advanced 1 term.
+        // Answers are provided to avoid JDK variations in Math.tan(x).
+        // https://keisan.casio.com/calculator
+        "0.5, 0.5463024898437905132552",
+        "0.125, 0.1256551365751309677927",
+        "0.0078125, 0.007812658949600007638479",
+    })
+    void testTanX2(double x, double expected) {
+        final double actual1 = GeneralizedContinuedFraction.value(0, new Tan(x));
+        Assertions.assertNotEquals(expected, actual1, "Standard formulation");
+        Assertions.assertEquals(expected, actual1, 2 * Math.ulp(expected));
+        // Advance 1 term
+        final Tan t = new Tan(x);
+        final Coefficient c = t.get();
+        final double actual2 = c.getA() / GeneralizedContinuedFraction.value(c.getB(), t);
+        Assertions.assertEquals(expected, actual2, "Revised formulation");
+    }
+
+    /**
+     * Test an invalid epsilon (big, very small, zero, negative or NaN).
+     * This test ensures the behaviour when passed an invalid epsilon is to attempt to
+     * converge rather than throw an exception.
+     * See NUMBERS-173.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {1.0, 0x1.0p-100, 0, -1, Double.NaN})
+    void testGoldenRatioAInvalidEpsilon(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(GoldenRatio.getInstance(), eps, 10000);
+        // An invalid epsilon is set to the default epsilon.
+        // It should converge to 1 ULP.
+        Assertions.assertEquals(GOLDEN_RATIO, actual, Math.ulp(GOLDEN_RATIO));
+    }
+
+    /**
+     * Test an invalid epsilon (big, very small, zero, negative or NaN).
+     * This test ensures the behaviour when passed an invalid epsilon is to attempt to
+     * converge rather than throw an exception.
+     * See NUMBERS-173.
+     */
+    @ParameterizedTest
+    @ValueSource(doubles = {1.0, 0x1.0p-100, 0, -1, Double.NaN})
+    void testGoldenRatioBInvalidEpsilon(double eps) {
+        final double actual = GeneralizedContinuedFraction.value(1, GoldenRatio.getInstance(), eps, 10000);
+        // An invalid epsilon is set to the default epsilon.
+        // It should converge to 1 ULP.
+        Assertions.assertEquals(GOLDEN_RATIO, actual, Math.ulp(GOLDEN_RATIO));
+    }
+
+    @Test
+    void test415Over93() {
+        // https://en.wikipedia.org/wiki/Continued_fraction
+        // 415             1
+        // ---  = 4 + ---------
+        //  93        2 +   1
+        //                -----
+        //                6 + 1
+        //                    -
+        //                    7
+        //      = [4; 2, 6, 7]
+
+        final Supplier<Coefficient> gen = simpleContinuedFraction(4, 2, 6, 7);
+
+        final double actual = GeneralizedContinuedFraction.value(gen);
+        final double expected = 415.0 / 93;
+        Assertions.assertEquals(expected, actual, Math.ulp(expected));
+    }
+
+    @Test
+    void test43Over93() {
+        // As per 415 / 93 but modified to remove leading 4 term.
+        // expected = (415 - 4 * 93) / 93 = 43 / 93
+
+        final Supplier<Coefficient> gen = simpleContinuedFraction(2, 6, 7);
+
+        // Evaluate with b0 = 0
+        final double actual = GeneralizedContinuedFraction.value(0, gen);
+        final double expected = 43.0 / 93;
+        Assertions.assertEquals(expected, actual, Math.ulp(expected));
+    }
+
+    @Test
+    void test0Over93() {
+        // As per 415 / 93 but modified to change the leading 4 term to be equal
+        // to the fraction component so the result is zero. This is a badly
+        // constructed simple continued fraction as the initial convergent (-43/93)
+        // is not an approximation for the result of 0. This tests the algorithm
+        // can converge close to zero.
+
+        final Supplier<Coefficient> gen = simpleContinuedFraction(-43.0 / 93, 2, 6, 7);
+
+        final double actual = GeneralizedContinuedFraction.value(gen);
+        Assertions.assertEquals(0, actual, Math.ulp(0.5));
+    }
+
+    @Test
+    void testMaxIterationsThrowsA() {
+        final double eps = Math.ulp(1.0);
+
+        // This works when enough terms are used
+        Assertions.assertEquals(GOLDEN_RATIO,
+            GeneralizedContinuedFraction.value(GoldenRatio.getInstance(), eps, 100), GOLDEN_RATIO * eps);
+
+        final GoldenRatio gr = GoldenRatio.getInstance();
+        final Throwable t = Assertions.assertThrows(ArithmeticException.class,
+            () -> GeneralizedContinuedFraction.value(gr, eps, 3));
+        assertExceptionMessageContains(t, "max");
+    }
+
+    @Test
+    void testMaxIterationsThrowsB() {
+        final double eps = Math.ulp(1.0);
+
+        // This works when enough terms are used
+        Assertions.assertEquals(INV_GOLDEN_RATIO,
+            GeneralizedContinuedFraction.value(0, GoldenRatio.getInstance(), eps, 100), INV_GOLDEN_RATIO * eps);
+
+        final GoldenRatio gr = GoldenRatio.getInstance();
+        final Throwable t = Assertions.assertThrows(ArithmeticException.class,
+            () -> GeneralizedContinuedFraction.value(0, gr, eps, 3));
+        assertExceptionMessageContains(t, "max");
+    }
+
+    @Test
+    void testNaNThrowsA() {
+        // Create a NaN during the iteration
+        final Supplier<Coefficient> gen = simpleContinuedFraction(1, 1, Double.NaN);
+
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(gen));
+        assertExceptionMessageContains(t, "nan");
+    }
+
+    @Test
+    void testNaNThrowsB() {
+        // Create a NaN during the iteration
+        final Supplier<Coefficient> gen = simpleContinuedFraction(1, 1, Double.NaN);
+
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(0, gen));
+        assertExceptionMessageContains(t, "nan");
+    }
+
+    /**
+     * Evaluate a fraction with very large numerator coefficient that creates infinity
+     * for the update step.
+     * <pre>
+     *                 max
+     *    0.5 + ---------------------
+     *          0.5 +      max
+     *                ---------------
+     *                0.5 +    max
+     *                      ---------
+     *                      0.5 + ...
+     * </pre>
+     */
+    @Test
+    void testInfThrowsA() {
+        // Create an infinity during the iteration:
+        // cN = b + a / cPrev => b_1 + a_1 / b_0 => 0.5 + Double.MAX_VALUE / 0.5
+        // deltaN = cN * dN -> infinity for the update
+        final Supplier<Coefficient> gen = () -> Coefficient.of(Double.MAX_VALUE, 0.5);
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(gen));
+        assertExceptionMessageContains(t, "infinity");
+    }
+
+    /**
+     * Evaluate a fraction with very large numerator coefficient that creates infinity
+     * for the update step.
+     * <pre>
+     *                 max
+     *    0.0 + ---------------------
+     *          0.5 +      max
+     *                ---------------
+     *                0.5 +    max
+     *                      ---------
+     *                      0.5 + ...
+     * </pre>
+     */
+    @Test
+    void testInfThrowsB() {
+        // Create an infinity during the iteration.
+        // This is created as per the method above as the continued fraction
+        // is evaluated with the same terms and divided into a0.
+
+        final Supplier<Coefficient> gen = () -> Coefficient.of(Double.MAX_VALUE, 0.5);
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(0, gen));
+        assertExceptionMessageContains(t, "infinity");
+    }
+
+    /**
+     * Evaluate a fraction with very large numerator coefficient that creates zero for
+     * the update step.
+     * <pre>
+     *                   1
+     *    0.5 + ---------------------
+     *          0.5 +       max
+     *                ---------------
+     *                0.5 +    max
+     *                      ---------
+     *                      0.5 + ...
+     * </pre>
+     */
+    @Test
+    void testZeroThrowsA() {
+        // Create an infinity during the iteration:
+        // dN = b + a * dPrev => b_2 + a_2 * 2.0 => 0.5 + Double.MAX_VALUE * 2.0
+        // deltaN = cN * 1/dN -> 0 for the update.
+
+        final Supplier<Coefficient> gen = new Supplier<Coefficient>() {
+            private int n;
+
+            @Override
+            public Coefficient get() {
+                final double a = n++ < 2 ? 1 : Double.MAX_VALUE;
+                return Coefficient.of(a, 0.5);
+            }
+        };
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(gen, 1e-10, 100));
+
+        // If the zero update step is not detected with the current coefficients
+        // of (max, 0.5) the algorithm continues up to the maximum iterations.
+        assertExceptionMessageContains(t, "zero");
+    }
+
+    /**
+     * Evaluate a fraction with very large numerator coefficient that creates zero for
+     * the update step.
+     * <pre>
+     *                   1
+     *    0.0 + ---------------------
+     *          0.5 +       1
+     *                ---------------
+     *                0.5 +    max
+     *                      ---------
+     *                      0.5 + ...
+     * </pre>
+     */
+    @Test
+    void testZeroThrowsB() {
+        // Create an infinity during the iteration.
+        // This is created as per the method above as the continued fraction
+        // is evaluated with the same terms and divided into a0.
+
+        final Supplier<Coefficient> gen = new Supplier<Coefficient>() {
+            private int n;
+
+            @Override
+            public Coefficient get() {
+                final double a = n++ < 2 ? 1 : Double.MAX_VALUE;
+                return Coefficient.of(a, 0.5);
+            }
+        };
+        final Throwable t = Assertions.assertThrows(FractionException.class,
+            () -> GeneralizedContinuedFraction.value(0, gen, 1e-10, 100));
+
+        // If the zero update step is not detected with the current coefficients
+        // of (max, 0.5) the algorithm continues up to the maximum iterations.
+        assertExceptionMessageContains(t, "zero");
+    }
+
+    private static void assertExceptionMessageContains(Throwable t, String text) {
+        Assertions.assertTrue(t.getMessage().toLowerCase(Locale.ROOT).contains(text),
+            () -> "Missing '" + text + "' from exception message: " + t.getMessage());
+    }
+
+    /**
+     * Evaluate a fraction where the initial term is negative and close to zero.
+     * The algorithm should be set the initial term to a small value
+     * of the same sign. The current close to zero threshold is 1e-50.
+     * Ensure the magnitude of the fraction component is larger. If smaller then the result
+     * will always be around the small threshold of 1e-50.
+     * <pre>
+     *              1
+     *    -1e-51 - ----
+     *             1e49
+     * </pre>
+     * <p>If the sign is not preserved the result is incorrect.
+     */
+    @Test
+    void testCloseToZeroB0() {
+        final double b0 = -1e-51;
+        final double b1 = -1e49;
+
+        final double expected = b0 + 1 / b1;
+        // Recommended when b0 is small to pass this separately
+        Assertions.assertEquals(expected, GeneralizedContinuedFraction.value(b0, simpleContinuedFraction(b1)));
+        // This should be close. The sign should not be different.
+        Assertions.assertEquals(expected,
+            GeneralizedContinuedFraction.value(simpleContinuedFraction(b0, b1)), Math.abs(expected) * 0.1);
+
+        // Check the negation
+        Assertions.assertEquals(-expected, GeneralizedContinuedFraction.value(-b0, simpleContinuedFraction(-b1)));
+        Assertions.assertEquals(-expected,
+            GeneralizedContinuedFraction.value(simpleContinuedFraction(-b0, -b1)), Math.abs(expected) * 0.1);
+    }
+
+    /**
+     * Evaluate simple continued fractions.
+     */
+    @ParameterizedTest
+    @CsvSource(value = {
+        // Generated using matlab, e.g. rat(34.567, 1e-15)
+        "34.567, 35:-2:-3:-4:-3:-10",
+        "-4.788, -5:5:-4:2:7",
+        "67.492, 67:2:31:-4",
+        "0.6782348, 1:-3:-9:-4:3:7:-19:3:-2:-11",
+    })
+    void testSimpleContinuedFraction(double x, String scf) {
+        final double[] b = Arrays.stream(scf.split(":")).mapToDouble(Double::parseDouble).toArray();
+        Assertions.assertEquals(x,
+            GeneralizedContinuedFraction.value(simpleContinuedFraction(b)), Math.abs(x) * 1e-15);
+    }
+
+    /**
+     * Test using an epsilon that converges after one iteration.
+     * This should not raise an exception.
+     * See NUMBERS-46.
+     */
+    @Test
+    void testOneIteration() {
+        final double eps = 0.5;
+        final double gr = GeneralizedContinuedFraction.value(GoldenRatio.getInstance(), eps, 1);
+        // Expected: 1 + 1 / 1
+        Assertions.assertEquals(2.0, gr);
+    }
+
+    /**
+     * Test using an epsilon that converges after two iterations.
+     * This should not raise an exception.
+     * See NUMBERS-46.
+     */
+    @Test
+    void testTwoIterations() {
+        final double eps = 0.25;
+        final double gr = GeneralizedContinuedFraction.value(GoldenRatio.getInstance(), eps, 2);
+        // Expected: 1 + 1 / (1 + 1 / 1)
+        Assertions.assertEquals(1.5, gr);
+    }
+
+    /**
+     * Series to compute the golden ratio:<br>
+     * https://mathworld.wolfram.com/GoldenRatio.html<br>
+     * Continued fraction eq. 17.
+     * <pre>
+     *                1
+     * GR = 1 + ---------------
+     *          1 +      1
+     *              -----------
+     *              1 +    1
+     *                  -------
+     *                  1 + ...
+     * </pre>
+     */
+    private static class GoldenRatio implements Supplier<Coefficient> {
+        private static final GoldenRatio INSTANCE = new GoldenRatio();
+        private static final Coefficient RESULT = Coefficient.of(1, 1);
+
+        /**
+         * @return single instance of GoldenRatio
+         */
+        static GoldenRatio getInstance() {
+            return INSTANCE;
+        }
+
+        @Override
+        public Coefficient get() {
+            return RESULT;
+        }
+    }
+
+    /**
+     * Series to compute the base of the natural logarithm e:<br>
+     * https://en.wikipedia.org/wiki/Continued_fraction#Regular_patterns_in_continued_fractions<br>
+     * <pre>
+     * e = [2; 1, 2, 1, 1, 4, 1, 1, 6, 1, ...]
+     * </pre>
+     * <p>The term 2 must be added to the generated series.
+     */
+    private static class E implements Supplier<Coefficient> {
+        private int n;
+
+        @Override
+        public Coefficient get() {
+            n++;
+            // Repeating series of 3:
+            // [1, t, 1] where t = 2 + 2 n/3
+            final double t = n % 3 == 2 ? 2 + 2 * (n / 3) : 1;
+            return Coefficient.of(1, t);
+        }
+    }
+
+    /**
+     * Series to compute tan(x):<br>
+     * https://www.boost.org/doc/libs/1_77_0/libs/math/doc/html/math_toolkit/internals/cf.html<br/>
+     * <pre>
+     *                z
+     * tan(z) = ---------------
+     *          1 -     z^2
+     *              -----------
+     *              3 -   z^2
+     *                  -------
+     *                  5 - ...
+     * </pre>
+     */
+    private static class Tan implements Supplier<Coefficient> {
+        private double a;
+        private double b = -1;
+
+        /**
+         * @param z Argument z
+         */
+        Tan(double z) {
+            a = z;
+        }
+
+        @Override
+        public Coefficient get() {
+            b += 2;
+            // Special first case
+            if (b == 1) {
+                final double z = a;
+                // Update the term for the rest of the fraction.
+                // The continuant is subtracted from the b terms, thus all the
+                // remaining a terms are negative.
+                a *= -z;
+                return Coefficient.of(z, b);
+            }
+            return Coefficient.of(a, b);
+        }
+    }
+
+    /**
+     * Create a generator for a simple continued fraction.
+     * <pre>
+     *              1
+     * b0 + ------------------
+     *      b1 +      1
+     *           -------------
+     *           b2 +    1
+     *                --------
+     *                b3 + ...
+     * </pre>
+     *
+     * @param coefficients b coefficients
+     * @return the generator
+     */
+    private static Supplier<Coefficient> simpleContinuedFraction(double... coefficients) {
+        return new Supplier<Coefficient>() {
+            /* iteration. */
+            private int n;
+            /* denominator terms. */
+            private final double[] b = coefficients.clone();
+
+            @Override
+            public Coefficient get() {
+                if (n != b.length) {
+                    // Return the next term
+                    return Coefficient.of(1, b[n++]);
+                }
+                return Coefficient.of(0, 1);
+            }
+        };
+    }
+}

[commons-numbers] 04/04: Add usable maven modules to the main README.md page

Posted by ah...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-numbers.git

commit 4d5258e7a6412bbc7628cab710cbf4e68c6bfe35
Author: aherbert <ah...@apache.org>
AuthorDate: Wed Nov 24 12:59:43 2021 +0000

    Add usable maven modules to the main README.md page
    
    This replaces the example for commons-numbers-parent
---
 README.md | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e2bdb1e..2bb6f13 100644
--- a/README.md
+++ b/README.md
@@ -75,12 +75,22 @@ Where can I get the latest release?
 -----------------------------------
 You can download source and binaries from our [download page](https://commons.apache.org/proper/commons-numbers/download_numbers.cgi).
 
-Alternatively you can pull it from the central Maven repositories:
+Alternatively you can pull it from the central Maven repositories, for example:
 
 ```xml
 <dependency>
   <groupId>org.apache.commons</groupId>
-  <artifactId>commons-numbers-parent</artifactId>
+  <artifactId>commons-numbers-core</artifactId>
+  <version>1.0</version>
+</dependency>
+<dependency>
+  <groupId>org.apache.commons</groupId>
+  <artifactId>commons-numbers-complex</artifactId>
+  <version>1.0</version>
+</dependency>
+<dependency>
+  <groupId>org.apache.commons</groupId>
+  <artifactId>commons-numbers-quaternion</artifactId>
   <version>1.0</version>
 </dependency>
 ```