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
The following commit(s) were added to refs/heads/master by this push:
new 274818c NUMBERS-178: Tabulate all representable factorials in
FactorialDouble
274818c is described below
commit 274818ccab651e51a47985a1ea54ef069b524adb
Author: aherbert <[email protected]>
AuthorDate: Fri Dec 3 13:47:57 2021 +0000
NUMBERS-178: Tabulate all representable factorials in FactorialDouble
Values are verified using BigInteger computation of n!
This change deprecates the now redundant caching functionality.
---
.../numbers/combinatorics/FactorialDouble.java | 295 ++++++++++++++-------
.../numbers/combinatorics/FactorialDoubleTest.java | 122 ++-------
src/changes/changes.xml | 5 +
3 files changed, 227 insertions(+), 195 deletions(-)
diff --git
a/commons-numbers-combinatorics/src/main/java/org/apache/commons/numbers/combinatorics/FactorialDouble.java
b/commons-numbers-combinatorics/src/main/java/org/apache/commons/numbers/combinatorics/FactorialDouble.java
index 260a5bb..6b0baf0 100644
---
a/commons-numbers-combinatorics/src/main/java/org/apache/commons/numbers/combinatorics/FactorialDouble.java
+++
b/commons-numbers-combinatorics/src/main/java/org/apache/commons/numbers/combinatorics/FactorialDouble.java
@@ -20,86 +20,209 @@ package org.apache.commons.numbers.combinatorics;
/**
* Class for computing the natural logarithm of the
* <a href="http://mathworld.wolfram.com/Factorial.html">factorial of a
number</a>.
- * It allows to allocate a cache of precomputed values.
*/
public final class FactorialDouble {
- /**
- * Size of precomputed factorials.
- * @see Factorial
- */
- private static final int FACTORIALS_LONG_CACHE_SIZE = 21;
- /**
- * Maximum representable factorial. Any factorial above this will overflow
a {@code double}.
- */
- private static final int MAX_FACTORIAL = 170;
- /**
- * Precomputed values of the function: {@code factorialsDouble[i] = i!}.
- */
- private final double[] factorialsDouble;
-
- /**
- * Creates an instance, reusing the already computed values if available.
- *
- * @param numValues Number of values of the function to compute.
- * @param cache Cached values.
- * @throw IllegalArgumentException if {@code n < 0}.
- */
- private FactorialDouble(int numValues,
- double[] cache) {
- if (numValues < 0) {
- throw new CombinatoricsException(CombinatoricsException.NEGATIVE,
numValues);
- }
-
- // Avoid caching values that are infinite
- final int num = Math.min(numValues, MAX_FACTORIAL + 1);
-
- factorialsDouble = new double[num];
- // Initialize first two entries.
- final int max = num < 2 ? num : 2;
- for (int i = 0; i < max; i++) {
- factorialsDouble[i] = 1;
- }
-
- final int beginCopy = 2;
- int endCopy;
- if (cache == null || cache.length <= beginCopy) {
- endCopy = beginCopy;
- } else if (cache.length <= num) {
- endCopy = cache.length;
- } else {
- endCopy = num;
- }
-
- // Copy available values.
- for (int i = beginCopy; i < endCopy; i++) {
- factorialsDouble[i] = cache[i];
- }
-
- // Precompute.
- for (int i = endCopy; i < num; i++) {
- factorialsDouble[i] = i * factorialsDouble[i - 1];
- }
- }
+ /** All factorials that can be represented as a double (values up to
170!). */
+ private static final double[] FACTORIAL = {
+ 1,
+ 1,
+ 2,
+ 6,
+ 24,
+ 120,
+ 720,
+ 5040,
+ 40320,
+ 362880,
+ 3628800,
+ 3.99168E7,
+ 4.790016E8,
+ 6.2270208E9,
+ 8.71782912E10,
+ 1.307674368E12,
+ 2.0922789888E13,
+ 3.55687428096E14,
+ 6.402373705728E15,
+ 1.21645100408832E17,
+ 2.43290200817664E18,
+ 5.109094217170944E19,
+ 1.1240007277776077E21,
+ 2.585201673888498E22,
+ 6.204484017332394E23,
+ 1.5511210043330986E25,
+ 4.0329146112660565E26,
+ 1.0888869450418352E28,
+ 3.0488834461171387E29,
+ 8.841761993739702E30,
+ 2.6525285981219107E32,
+ 8.222838654177922E33,
+ 2.631308369336935E35,
+ 8.683317618811886E36,
+ 2.9523279903960416E38,
+ 1.0333147966386145E40,
+ 3.7199332678990125E41,
+ 1.3763753091226346E43,
+ 5.230226174666011E44,
+ 2.0397882081197444E46,
+ 8.159152832478977E47,
+ 3.345252661316381E49,
+ 1.40500611775288E51,
+ 6.041526306337383E52,
+ 2.658271574788449E54,
+ 1.1962222086548019E56,
+ 5.502622159812089E57,
+ 2.5862324151116818E59,
+ 1.2413915592536073E61,
+ 6.082818640342675E62,
+ 3.0414093201713376E64,
+ 1.5511187532873822E66,
+ 8.065817517094388E67,
+ 4.2748832840600255E69,
+ 2.308436973392414E71,
+ 1.2696403353658276E73,
+ 7.109985878048635E74,
+ 4.0526919504877214E76,
+ 2.3505613312828785E78,
+ 1.3868311854568984E80,
+ 8.32098711274139E81,
+ 5.075802138772248E83,
+ 3.146997326038794E85,
+ 1.98260831540444E87,
+ 1.2688693218588417E89,
+ 8.247650592082472E90,
+ 5.443449390774431E92,
+ 3.647111091818868E94,
+ 2.4800355424368305E96,
+ 1.711224524281413E98,
+ 1.1978571669969892E100,
+ 8.504785885678623E101,
+ 6.1234458376886085E103,
+ 4.4701154615126844E105,
+ 3.307885441519386E107,
+ 2.48091408113954E109,
+ 1.8854947016660504E111,
+ 1.4518309202828587E113,
+ 1.1324281178206297E115,
+ 8.946182130782976E116,
+ 7.156945704626381E118,
+ 5.797126020747368E120,
+ 4.753643337012842E122,
+ 3.945523969720659E124,
+ 3.314240134565353E126,
+ 2.81710411438055E128,
+ 2.4227095383672734E130,
+ 2.107757298379528E132,
+ 1.8548264225739844E134,
+ 1.650795516090846E136,
+ 1.4857159644817615E138,
+ 1.352001527678403E140,
+ 1.2438414054641308E142,
+ 1.1567725070816416E144,
+ 1.087366156656743E146,
+ 1.032997848823906E148,
+ 9.916779348709496E149,
+ 9.619275968248212E151,
+ 9.426890448883248E153,
+ 9.332621544394415E155,
+ 9.332621544394415E157,
+ 9.42594775983836E159,
+ 9.614466715035127E161,
+ 9.90290071648618E163,
+ 1.0299016745145628E166,
+ 1.081396758240291E168,
+ 1.1462805637347084E170,
+ 1.226520203196138E172,
+ 1.324641819451829E174,
+ 1.4438595832024937E176,
+ 1.588245541522743E178,
+ 1.7629525510902446E180,
+ 1.974506857221074E182,
+ 2.2311927486598138E184,
+ 2.5435597334721877E186,
+ 2.925093693493016E188,
+ 3.393108684451898E190,
+ 3.969937160808721E192,
+ 4.684525849754291E194,
+ 5.574585761207606E196,
+ 6.689502913449127E198,
+ 8.094298525273444E200,
+ 9.875044200833601E202,
+ 1.214630436702533E205,
+ 1.506141741511141E207,
+ 1.882677176888926E209,
+ 2.372173242880047E211,
+ 3.0126600184576594E213,
+ 3.856204823625804E215,
+ 4.974504222477287E217,
+ 6.466855489220474E219,
+ 8.47158069087882E221,
+ 1.1182486511960043E224,
+ 1.4872707060906857E226,
+ 1.9929427461615188E228,
+ 2.6904727073180504E230,
+ 3.659042881952549E232,
+ 5.012888748274992E234,
+ 6.917786472619489E236,
+ 9.615723196941089E238,
+ 1.3462012475717526E241,
+ 1.898143759076171E243,
+ 2.695364137888163E245,
+ 3.854370717180073E247,
+ 5.5502938327393044E249,
+ 8.047926057471992E251,
+ 1.1749972043909107E254,
+ 1.727245890454639E256,
+ 2.5563239178728654E258,
+ 3.80892263763057E260,
+ 5.713383956445855E262,
+ 8.62720977423324E264,
+ 1.3113358856834524E267,
+ 2.0063439050956823E269,
+ 3.0897696138473508E271,
+ 4.789142901463394E273,
+ 7.471062926282894E275,
+ 1.1729568794264145E278,
+ 1.853271869493735E280,
+ 2.9467022724950384E282,
+ 4.7147236359920616E284,
+ 7.590705053947219E286,
+ 1.2296942187394494E289,
+ 2.0044015765453026E291,
+ 3.287218585534296E293,
+ 5.423910666131589E295,
+ 9.003691705778438E297,
+ 1.503616514864999E300,
+ 2.5260757449731984E302,
+ 4.269068009004705E304,
+ 7.257415615307999E306
+ };
+
+ /** Single instance. */
+ private static final FactorialDouble INSTANCE = new FactorialDouble();
+
+ /** No public instances. */
+ private FactorialDouble() {}
/**
- * Creates an instance with no precomputed values.
- * @return instance with no precomputed values
+ * Creates an instance.
+ * @return instance
*/
public static FactorialDouble create() {
- return new FactorialDouble(0, null);
+ return INSTANCE;
}
/**
- * Creates an instance with the specified cache size.
+ * @deprecated Since 1.1 the cache size argument is ignored. Use {@link
#create()} to obtain an instance.
+ *
+ * <p>As of Numbers 1.1 all representable factorials up to {@code n=170}
are tabulated.
+ * This method returns a reference to the same object.
*
- * @param cacheSize Number of precomputed values of the function.
- * @return a new instance where {@code cacheSize} values have been
- * precomputed.
- * @throws IllegalArgumentException if {@code cacheSize < 0}.
+ * @param cacheSize Ignored.
+ * @return instance
*/
+ @Deprecated
public FactorialDouble withCache(final int cacheSize) {
- return new FactorialDouble(cacheSize,
- factorialsDouble);
+ return this;
}
/**
@@ -113,38 +236,14 @@ public final class FactorialDouble {
* @return {@code n!}
* @throws IllegalArgumentException if {@code n < 0}.
*/
+ // Note: This must remain an instance method for binary compatibility
public double value(int n) {
- if (n < FACTORIALS_LONG_CACHE_SIZE) {
- return Factorial.value(n);
- }
-
- if (n < factorialsDouble.length) {
- // Use cache of precomputed values.
- return factorialsDouble[n];
- }
-
- return compute(n);
- }
-
- /**
- * @param n Argument.
- * @return {@code n!} (approximated as a {@code double}).
- */
- private double compute(int n) {
- if (n > MAX_FACTORIAL) {
- return Double.POSITIVE_INFINITY;
- }
-
- int start = 2;
- double result = 1;
-
- if (factorialsDouble.length > 2) {
- result = factorialsDouble[factorialsDouble.length - 1];
- start = factorialsDouble.length;
+ if (n < 0) {
+ throw new CombinatoricsException(CombinatoricsException.NEGATIVE,
n);
}
- for (int i = start; i <= n; i++) {
- result *= i;
+ if (n < FACTORIAL.length) {
+ return FACTORIAL[n];
}
- return result;
+ return Double.POSITIVE_INFINITY;
}
}
diff --git
a/commons-numbers-combinatorics/src/test/java/org/apache/commons/numbers/combinatorics/FactorialDoubleTest.java
b/commons-numbers-combinatorics/src/test/java/org/apache/commons/numbers/combinatorics/FactorialDoubleTest.java
index 40a7c00..23bc7b4 100644
---
a/commons-numbers-combinatorics/src/test/java/org/apache/commons/numbers/combinatorics/FactorialDoubleTest.java
+++
b/commons-numbers-combinatorics/src/test/java/org/apache/commons/numbers/combinatorics/FactorialDoubleTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.numbers.combinatorics;
+import java.math.BigInteger;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -23,49 +24,15 @@ import org.junit.jupiter.api.Test;
* Test cases for the {@link FactorialDouble} class.
*/
class FactorialDoubleTest {
+ /** The largest representable factorial (n=170). */
+ private static final int MAX_N = 170;
+
@Test
void testFactorialZero() {
Assertions.assertEquals(1, FactorialDouble.create().value(0), "0!");
}
@Test
- void testFactorialDirect() {
- for (int i = 1; i < 21; i++) {
- Assertions.assertEquals(
- factorialDirect(i), FactorialDouble.create().value(i), i +
"!");
- }
- }
-
- @Test
- void testLargestFactorialDouble() {
- final int n = 170;
- Assertions.assertNotEquals(
- Double.POSITIVE_INFINITY, FactorialDouble.create().value(n), () ->
n + "!");
- }
-
- @Test
- void testFactorialDoubleTooLarge() {
- final int n = 171;
- // Verify the limit. Start at the largest representable factorial as a
long: 20!
- double f = 2432902008176640000L;
- for (int i = 21; i < n; i++) {
- f *= i;
- }
- Assertions.assertTrue(Double.isFinite(f), "170! is finite");
- f *= n;
- Assertions.assertFalse(Double.isFinite(f), "171! is infinite");
- Assertions.assertEquals(
- Double.POSITIVE_INFINITY, FactorialDouble.create().value(n),
() -> n + "!");
- }
-
- @Test
- void testNonPositiveArgumentWithCache() {
- Assertions.assertThrows(IllegalArgumentException.class,
- () -> FactorialDouble.create().withCache(-1)
- );
- }
-
- @Test
void testNonPositiveArgument() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> FactorialDouble.create().value(-1)
@@ -73,76 +40,37 @@ class FactorialDoubleTest {
}
@Test
- void testCompareDirectWithoutCache() {
- // This test shows that delegating to the "Gamma" class will also lead
to a
- // less accurate result.
-
- final int max = 100;
+ void testFactorials() {
final FactorialDouble f = FactorialDouble.create();
-
- for (int i = 0; i < max; i++) {
- final double expected = factorialDirect(i);
- Assertions.assertEquals(
- expected, f.value(i), 100 * Math.ulp(expected), i + "! ");
+ // Start at 0!
+ BigInteger value = BigInteger.ONE;
+ for (int n = 1; n <= MAX_N; n++) {
+ // n! = (n-1)! * n
+ value = value.multiply(BigInteger.valueOf(n));
+ Assertions.assertEquals(value.doubleValue(), f.value(n));
}
}
@Test
- void testCompareDirectWithCache() {
- final int max = 100;
- final FactorialDouble f = FactorialDouble.create().withCache(max);
-
- for (int i = 0; i < max; i++) {
- final double expected = factorialDirect(i);
- Assertions.assertEquals(
- expected, f.value(i), 100 * Math.ulp(expected), i + "! ");
+ void testFactorialDoubleTooLarge() {
+ final FactorialDouble f = FactorialDouble.create();
+ // Long avoids overflow to negative
+ for (long n = MAX_N + 1; n < Integer.MAX_VALUE; n *= 2) {
+ Assertions.assertEquals(Double.POSITIVE_INFINITY, f.value((int)
n));
}
}
- @Test
- void testCacheIncrease() {
- final int max = 100;
- final FactorialDouble f1 = FactorialDouble.create().withCache(max);
- final FactorialDouble f2 = f1.withCache(2 * max);
-
- final int val = max + max / 2;
- Assertions.assertEquals(f1.value(val), f2.value(val));
- }
-
- @Test
- void testZeroCache() {
- // Ensure that no exception is thrown.
- final FactorialDouble f = FactorialDouble.create().withCache(0);
- Assertions.assertEquals(1, f.value(0));
- Assertions.assertEquals(1, f.value(1));
- }
-
- @Test
- void testUselessCache() {
- Assertions.assertDoesNotThrow(() -> {
- LogFactorial.create().withCache(1);
- LogFactorial.create().withCache(2);
- });
- }
-
- @Test
- void testCacheDecrease() {
- final int max = 100;
- final FactorialDouble f1 = FactorialDouble.create().withCache(max);
- final FactorialDouble f2 = f1.withCache(max / 2);
-
- final int val = max / 4;
- Assertions.assertEquals(f1.value(val), f2.value(val));
- }
-
/**
- * Direct multiplication implementation.
+ * Test the {@link FactorialDouble#withCache(int)} method does nothing.
+ * This method was deprecated since 1.1.
+ * It now allows calling the method with an invalid cache size.
*/
- private double factorialDirect(int n) {
- double result = 1;
- for (int i = 2; i <= n; i++) {
- result *= i;
+ @SuppressWarnings("deprecation")
+ @Test
+ void testWithCacheReturnsThis() {
+ final FactorialDouble f = FactorialDouble.create();
+ for (int cacheSize : new int[] {-1, 0, 10000000}) {
+ Assertions.assertSame(f, f.withCache(cacheSize));
}
- return result;
}
}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 954c4f3..a0fc286 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -72,6 +72,11 @@ N.B. the Performance testing module requires Java 9+.
(The unit tests require Java 8+)
">
+ <action dev="aherbert" type="update" issue="NUMBERS-178">
+ "FactorialDouble": Tabulate all representable factorials. This change
deprecates the
+ caching of iteratively computed values with exact 64-bit double
representations of
+ n! up to n=170.
+ </action>
<action dev="aherbert" type="update" issue="NUMBERS-176">
"ContinuedFraction": Update to use a shared implementation with
GeneralizedContinuedFraction.
</action>