View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.sampling.distribution;
18  
19  import org.apache.commons.math3.special.Gamma;
20  import org.apache.commons.rng.sampling.distribution.InternalUtils.FactorialLog;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.Test;
23  
24  /**
25   * Test for the {@link InternalUtils}.
26   */
27  class InternalUtilsTest {
28      /** The maximum value for n! that is representable as a long. */
29      private static final int MAX_REPRESENTABLE = 20;
30  
31      @Test
32      void testFactorial() {
33          Assertions.assertEquals(1L, InternalUtils.factorial(0));
34          long result = 1;
35          for (int n = 1; n <= MAX_REPRESENTABLE; n++) {
36              result *= n;
37              Assertions.assertEquals(result, InternalUtils.factorial(n));
38          }
39      }
40  
41      @Test
42      void testFactorialThrowsWhenNegative() {
43          Assertions.assertThrows(IndexOutOfBoundsException.class,
44              () -> InternalUtils.factorial(-1));
45      }
46  
47      @Test
48      void testFactorialThrowsWhenNotRepresentableAsLong() {
49          Assertions.assertThrows(IndexOutOfBoundsException.class,
50              () -> InternalUtils.factorial(MAX_REPRESENTABLE + 1));
51      }
52  
53      @Test
54      void testFactorialLog() {
55          // Cache size allows some of the factorials to be cached and some
56          // to be under the precomputed factorials.
57          FactorialLog factorialLog = FactorialLog.create().withCache(MAX_REPRESENTABLE / 2);
58          Assertions.assertEquals(0, factorialLog.value(0), 1e-10);
59          for (int n = 1; n <= MAX_REPRESENTABLE + 5; n++) {
60              // Use Commons math to compute logGamma(1 + n);
61              double expected = Gamma.logGamma(1 + n);
62              Assertions.assertEquals(expected, factorialLog.value(n), 1e-10);
63          }
64      }
65  
66      @Test
67      void testFactorialLogCacheSizeAboveRepresentableFactorials() {
68          final int limit = MAX_REPRESENTABLE + 5;
69          FactorialLog factorialLog = FactorialLog.create().withCache(limit);
70          for (int n = MAX_REPRESENTABLE; n <= limit; n++) {
71              // Use Commons math to compute logGamma(1 + n);
72              double expected = Gamma.logGamma(1 + n);
73              Assertions.assertEquals(expected, factorialLog.value(n), 1e-10);
74          }
75      }
76  
77      @Test
78      void testFactorialLogCacheExpansion() {
79          // There is no way to determine if the cache values were reused but this test
80          // exercises the method to ensure it does not error.
81          final FactorialLog factorialLog = FactorialLog.create()
82                                                        // Edge case where cache should not be copied (<2)
83                                                        .withCache(1)
84                                                        // Expand
85                                                        .withCache(5)
86                                                        // Expand more
87                                                        .withCache(10)
88                                                        // Contract
89                                                        .withCache(5);
90          for (int n = 1; n <= 5; n++) {
91              // Use Commons math to compute logGamma(1 + n);
92              double expected = Gamma.logGamma(1 + n);
93              Assertions.assertEquals(expected, factorialLog.value(n), 1e-10);
94          }
95      }
96  
97      @Test
98      void testLogFactorialThrowsWhenNegative() {
99          final FactorialLog factorialLog = FactorialLog.create();
100         Assertions.assertThrows(IndexOutOfBoundsException.class,
101             () -> factorialLog.value(-1));
102     }
103 
104     @Test
105     void testLogFactorialWithCacheThrowsWhenNegative() {
106         final FactorialLog factorialLog = FactorialLog.create();
107         Assertions.assertThrows(NegativeArraySizeException.class,
108             () -> factorialLog.withCache(-1));
109     }
110 }