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.numbers.gamma;
18  
19  import java.io.IOException;
20  import java.math.BigDecimal;
21  import java.util.Arrays;
22  import java.util.function.DoubleBinaryOperator;
23  import java.util.function.DoubleUnaryOperator;
24  import org.apache.commons.numbers.gamma.BoostGamma.Lanczos;
25  import org.junit.jupiter.api.Assertions;
26  import org.junit.jupiter.api.MethodOrderer;
27  import org.junit.jupiter.api.Order;
28  import org.junit.jupiter.api.Test;
29  import org.junit.jupiter.api.TestMethodOrder;
30  import org.junit.jupiter.params.ParameterizedTest;
31  import org.junit.jupiter.params.provider.CsvSource;
32  import org.junit.jupiter.params.provider.EnumSource;
33  import org.junit.jupiter.params.provider.EnumSource.Mode;
34  import org.junit.jupiter.params.provider.ValueSource;
35  
36  /**
37   * Tests for {@link BoostGamma}. Special functions from {@link BoostMath} and {@link SpecialMath}
38   * are also tested as these are used within the {@link BoostGamma} class.
39   *
40   * <p>Note: Some resource data files used in these tests have been extracted
41   * from the Boost test files for the gamma functions.
42   */
43  @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
44  class BoostGammaTest {
45      /** All representable factorials. */
46      private static final double[] FACTORIAL = BoostGamma.getFactorials();
47      /** The threshold value for choosing the Lanczos approximation. */
48      private static final double LANCZOS_THRESHOLD = 20;
49      /** Value for the sqrt of the epsilon for relative error.
50       * This is equal to the Boost constant {@code boost::math::tools::root_epsilon<double>()}. */
51      private static final double ROOT_EPSILON = 1.4901161193847656E-8;
52      /** Approximate value for ln(Double.MAX_VALUE).
53       * This is equal to the Boost constant {@code boost::math::tools::log_max_value<double>()}. */
54      private static final int LOG_MAX_VALUE = 709;
55      /** Approximate value for ln(Double.MIN_VALUE).
56       * This is equal to the Boost constant {@code boost::math::tools::log_min_value<double>()}.
57       * No term {@code x} should be used in {@code exp(x)} if {@code x < LOG_MIN_VALUE} to avoid
58       * underflow to sub-normal or zero. */
59      private static final int LOG_MIN_VALUE = -708;
60      /** The largest factorial that can be represented as a double.
61       * This is equal to the Boost constant {@code boost::math::max_factorial<double>::value}. */
62      private static final int MAX_FACTORIAL = 170;
63      /** Euler's constant. */
64      private static final double EULER = 0.5772156649015328606065120900824024310;
65  
66      /** The Boost 1_77_0 condition to use the asymptotic approximation. */
67      private static final DoubleDoubleBiPredicate USE_ASYM_APPROX =
68          (a, x) -> (x > 1000) && ((a < x) || (Math.abs(a - 50) / x < 1));
69      /** Predicate to not use the asymptotic approximation. */
70      private static final DoubleDoubleBiPredicate NO_ASYM_APPROX = (a, x) -> false;
71      /** Predicate to use the asymptotic approximation. */
72      private static final DoubleDoubleBiPredicate ASYM_APPROX = (a, x) -> true;
73  
74      /** A predicate for two {@code double} arguments. */
75      private interface DoubleDoubleBiPredicate {
76          /**
77           * @param a Argument a
78           * @param b Argument b
79           * @return true if successful
80           */
81          boolean test(double a, double b);
82      }
83  
84      /** Define the expected error for a test. */
85      private interface TestError {
86          /**
87           * @return maximum allowed error
88           */
89          double getTolerance();
90  
91          /**
92           * @return maximum allowed RMS error
93           */
94          double getRmsTolerance();
95  
96          /**
97           * @param name name of the test error (used to provide a name for {@link #toString()})
98           * @param tolerance maximum allowed error
99           * @param rmsTolerance maximum allowed RMS error
100          * @return the test error
101          */
102         static TestError of(String name, double tolerance, double rmsTolerance) {
103             return new TestError() {
104                 @Override
105                 public String toString() {
106                     return name;
107                 }
108 
109                 @Override
110                 public double getTolerance() {
111                     return tolerance;
112                 }
113 
114                 @Override
115                 public double getRmsTolerance() {
116                     return rmsTolerance;
117                 }
118             };
119         }
120     }
121 
122     /**
123      * Define the test cases for each resource file.
124      * This encapsulates the function to test, the expected maximum and RMS error, and
125      * the resource file containing the data.
126      *
127      * <h2>Note on accuracy</h2>
128      *
129      * <p>The Boost functions use the default policy of internal promotion
130      * of double to long double if it offers more precision. Code comments
131      * in the implementations for the maximum error are using the defaults with
132      * promotion enabled where the error is 'effectively zero'. Java does not
133      * support long double computation. Tolerances have been set to allow tests to
134      * pass. Spot checks on larger errors have been verified against the reference
135      * implementation compiled with promotion of double <strong>disabled</strong>.
136      *
137      * @see <a href="https://www.boost.org/doc/libs/1_77_0/libs/math/doc/html/math_toolkit/relative_error.html">Relative error</a>
138      * @see <a href="https://www.boost.org/doc/libs/1_77_0/libs/math/doc/html/math_toolkit/pol_tutorial/policy_tut_defaults.html">Policy defaults</a>
139      */
140     private enum TestCase implements TestError {
141         // Note:
142         // The original Boost tgamma function is not as accurate as the
143         // NSWC Library of Mathematics Subroutines in the range [-20, 20].
144         // The default implementation uses long double to compute and
145         // performs a narrowing cast to the double result. Without this
146         // feature the method accuracy is reduced.
147         // The code is here for testing.
148 
149         /** gamma Boost near 0 data. */
150         TGAMMAO_NEAR_0(BoostGammaTest::tgammaOriginal, "gamma_near_0_data.csv", 3.3, 1.2),
151         /** gamma Boost near 1 data. */
152         TGAMMAO_NEAR_1(BoostGammaTest::tgammaOriginal, "gamma_near_1_data.csv", 3.3, 1.2),
153         /** gamma Boost near 2 data. */
154         TGAMMAO_NEAR_2(BoostGammaTest::tgammaOriginal, "gamma_near_2_data.csv", 4.8, 1.3),
155         /** gamma Boost near -10 data. */
156         TGAMMAO_NEAR_M10(BoostGammaTest::tgammaOriginal, "gamma_near_m10_data.csv", 2.5, 1.2),
157         /** gamma -20 to 0 data. */
158         TGAMMAO_M20_0(BoostGammaTest::tgammaOriginal, "gamma_m20_0_data.csv", 4.5, 1.4),
159         /** gamma 0 to 20 data. */
160         TGAMMAO_0_20(BoostGammaTest::tgammaOriginal, "gamma_0_20_data.csv", 3.2, 1.2),
161         /** gamma very near 0 data. */
162         TGAMMAO_VERY_NEAR_0(BoostGammaTest::tgammaOriginal, "gamma_very_near_0_data.csv", 3.3, 0.75),
163 
164         /** gamma Boost factorial data. */
165         TGAMMA_FACTORIALS(BoostGamma::tgamma, "gamma_factorials_data.csv", 2.5, 0.8),
166         /** gamma Boost near 0 data. */
167         TGAMMA_NEAR_0(BoostGamma::tgamma, "gamma_near_0_data.csv", 1.6, 0.7),
168         /** gamma Boost near 1 data. */
169         TGAMMA_NEAR_1(BoostGamma::tgamma, "gamma_near_1_data.csv", 1.3, 0.7),
170         /** gamma Boost near 2 data. */
171         TGAMMA_NEAR_2(BoostGamma::tgamma, "gamma_near_2_data.csv", 1.1, 0.6),
172         /** gamma Boost near -10 data. */
173         TGAMMA_NEAR_M10(BoostGamma::tgamma, "gamma_near_m10_data.csv", 1.8, 0.7),
174         /** gamma Boost near -55 data. */
175         TGAMMA_NEAR_M55(BoostGamma::tgamma, "gamma_near_m55_data.csv", 2.5, 1.2),
176         /** gamma -20 to 0 data. */
177         TGAMMA_M20_0(BoostGamma::tgamma, "gamma_m20_0_data.csv", 3, 0.8),
178         /** gamma 0 to 20 data. */
179         TGAMMA_0_20(BoostGamma::tgamma, "gamma_0_20_data.csv", 2, 0.65),
180         /** gamma 20 to 150 data. */
181         TGAMMA_20_150(BoostGamma::tgamma, "gamma_20_150_data.csv", 3.8, 1.2),
182         /** gamma 150 to 171 data. */
183         TGAMMA_150_171(BoostGamma::tgamma, "gamma_150_171_data.csv", 3.2, 1.2),
184         /** gamma very near 0 data. */
185         TGAMMA_VERY_NEAR_0(BoostGamma::tgamma, "gamma_very_near_0_data.csv", 3.8, 0.7),
186 
187         /** gamma Boost factorial data. */
188         LGAMMA_FACTORIALS(BoostGamma::lgamma, "gamma_factorials_data.csv", 2, 0.8, 0.125),
189         /** gamma Boost near 0 data. */
190         LGAMMA_NEAR_0(BoostGamma::lgamma, "gamma_near_0_data.csv", 2, 1.2, 0.5),
191         /** gamma Boost near 1 data. */
192         LGAMMA_NEAR_1(BoostGamma::lgamma, "gamma_near_1_data.csv", 2, 1.5, 0.7),
193         /** gamma Boost near 2 data. */
194         LGAMMA_NEAR_2(BoostGamma::lgamma, "gamma_near_2_data.csv", 2, 0.7, 0.2),
195         /** gamma Boost near -10 data. */
196         LGAMMA_NEAR_M10(BoostGamma::lgamma, "gamma_near_m10_data.csv", 2, 3, 0.99),
197         /** gamma Boost near -55 data. */
198         LGAMMA_NEAR_M55(BoostGamma::lgamma, "gamma_near_m55_data.csv", 2, 0.9, 0.45),
199         /** gamma -20 to 0 data. */
200         // The value -2.75 is low precision
201         LGAMMA_M20_0(BoostGamma::lgamma, "gamma_m20_0_data.csv", 2, 100, 9),
202         /** gamma 0 to 20 data. */
203         LGAMMA_0_20(BoostGamma::lgamma, "gamma_0_20_data.csv", 2, 0.95, 0.25),
204         /** gamma 20 to 150 data. */
205         LGAMMA_20_150(BoostGamma::lgamma, "gamma_20_150_data.csv", 2, 1.5, 0.45),
206         /** gamma 150 to 171 data. */
207         LGAMMA_150_171(BoostGamma::lgamma, "gamma_150_171_data.csv", 2, 1.6, 0.65),
208         /** gamma very near 0 data. */
209         LGAMMA_VERY_NEAR_0(BoostGamma::lgamma, "gamma_very_near_0_data.csv", 2, 1.8, 0.4),
210 
211         /** gamma(1+x) - 1 Boost data. */
212         TGAMMAP1M1(BoostGamma::tgamma1pm1, "gamma1pm1_data.csv", 1.8, 0.6),
213 
214         /** log(1+x) - 1  data. */
215         LOG1PMX(SpecialMath::log1pmx, "log1pmx_data.csv", -0.9, 0.15);
216 
217         /** The function. */
218         private final DoubleUnaryOperator fun;
219 
220         /** The filename containing the test data. */
221         private final String filename;
222 
223         /** The field containing the expected value. */
224         private final int expected;
225 
226         /** The maximum allowed ulp. */
227         private final double maxUlp;
228 
229         /** The maximum allowed RMS ulp. */
230         private final double rmsUlp;
231 
232         /**
233          * Instantiates a new test case.
234          *
235          * @param fun function to test
236          * @param filename Filename of the test data
237          * @param maxUlp maximum allowed ulp
238          * @param rmsUlp maximum allowed RMS ulp
239          */
240         TestCase(DoubleUnaryOperator fun, String filename, double maxUlp, double rmsUlp) {
241             this(fun, filename, 1, maxUlp, rmsUlp);
242         }
243 
244         /**
245          * Instantiates a new test case.
246          *
247          * @param fun function to test
248          * @param filename Filename of the test data
249          * @param expected Expected result field index
250          * @param maxUlp maximum allowed ulp
251          * @param rmsUlp maximum allowed RMS ulp
252          */
253         TestCase(DoubleUnaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
254             this.fun = fun;
255             this.filename = filename;
256             this.expected = expected;
257             this.maxUlp = maxUlp;
258             this.rmsUlp = rmsUlp;
259         }
260 
261         /**
262          * @return function to test
263          */
264         DoubleUnaryOperator getFunction() {
265             return fun;
266         }
267 
268         /**
269          * @return Filename of the test data
270          */
271         String getFilename() {
272             return filename;
273         }
274 
275         /**
276          * @return Expected result field index
277          */
278         int getExpectedField() {
279             return expected;
280         }
281 
282         @Override
283         public double getTolerance() {
284             return maxUlp;
285         }
286 
287         @Override
288         public double getRmsTolerance() {
289             return rmsUlp;
290         }
291     }
292 
293     /**
294      * Define the test cases for each resource file for two argument functions.
295      * This encapsulates the function to test, the expected maximum and RMS error, and
296      * the resource file containing the data.
297      */
298     private enum BiTestCase implements TestError {
299         /** pow(x, y) - 1 Boost data. */
300         POWM1(BoostMath::powm1, "powm1_data.csv", 2.6, 0.4),
301         /** igamma Boost int data. */
302         IGAMMA_UPPER_INT(BoostGamma::tgamma, "igamma_int_data.csv", 6, 1.5),
303         /** igamma Boost small data. */
304         IGAMMA_UPPER_SMALL(BoostGamma::tgamma, "igamma_small_data.csv", 2.5, 0.9),
305         /** igamma Boost med data. */
306         IGAMMA_UPPER_MED(BoostGamma::tgamma, "igamma_med_data.csv", 9, 1.85),
307         /** igamma Boost big data. */
308         IGAMMA_UPPER_BIG(BoostGamma::tgamma, "igamma_big_data.csv", 8, 1.3),
309         /** igamma extra data containing edge cases. */
310         IGAMMA_UPPER_EXTRA(BoostGamma::tgamma, "igamma_extra_data.csv", 13, 4),
311         /** igamma Boost int data. */
312         IGAMMA_Q_INT(BoostGamma::gammaQ, "igamma_int_data.csv", 3, 5, 1.3),
313         /** igamma Boost small data. */
314         IGAMMA_Q_SMALL(BoostGamma::gammaQ, "igamma_small_data.csv", 3, 4, 1.1),
315         /** igamma Boost med data. */
316         IGAMMA_Q_MED(BoostGamma::gammaQ, "igamma_med_data.csv", 3, 6, 0.95),
317         /** igamma Boost big data. */
318         IGAMMA_Q_BIG(BoostGamma::gammaQ, "igamma_big_data.csv", 3, 550, 62),
319         /** igamma extra data containing edge cases. */
320         IGAMMA_Q_EXTRA(BoostGamma::gammaQ, "igamma_extra_data.csv", 3, 60, 18),
321         /** igamma Boost int data. */
322         IGAMMA_LOWER_INT(BoostGamma::tgammaLower, "igamma_int_data.csv", 4, 6, 1.3),
323         /** igamma Boost small data. */
324         IGAMMA_LOWER_SMALL(BoostGamma::tgammaLower, "igamma_small_data.csv", 4, 1.6, 0.55),
325         /** igamma Boost med data. */
326         IGAMMA_LOWER_MED(BoostGamma::tgammaLower, "igamma_med_data.csv", 4, 6.5, 1.3),
327         /** igamma Boost big data. */
328         IGAMMA_LOWER_BIG(BoostGamma::tgammaLower, "igamma_big_data.csv", 4, 8, 1.3),
329         /** igamma extra data containing edge cases. */
330         IGAMMA_LOWER_EXTRA(BoostGamma::tgammaLower, "igamma_extra_data.csv", 4, 5, 1.4),
331         /** igamma Boost int data. */
332         IGAMMA_P_INT(BoostGamma::gammaP, "igamma_int_data.csv", 5, 3.5, 0.95),
333         /** igamma Boost small data. */
334         IGAMMA_P_SMALL(BoostGamma::gammaP, "igamma_small_data.csv", 5, 2.8, 0.9),
335         /** igamma Boost med data. */
336         IGAMMA_P_MED(BoostGamma::gammaP, "igamma_med_data.csv", 5, 5, 0.9),
337         /** igamma Boost big data. */
338         IGAMMA_P_BIG(BoostGamma::gammaP, "igamma_big_data.csv", 5, 430, 55),
339         /** igamma extra data containing edge cases. */
340         IGAMMA_P_EXTRA(BoostGamma::gammaP, "igamma_extra_data.csv", 5, 0.7, 0.2),
341         /** gamma p derivative computed for igamma Boost int data. */
342         GAMMA_P_DERIV_INT(BoostGamma::gammaPDerivative, "igamma_int_data_p_derivative.csv", 3.5, 1.1),
343         /** gamma p derivative computed for igamma Boost small data. */
344         GAMMA_P_DERIV_SMALL(BoostGamma::gammaPDerivative, "igamma_small_data_p_derivative.csv", 3.3, 0.99),
345         /** gamma p derivative computed for igamma Boost med data. */
346         GAMMA_P_DERIV_MED(BoostGamma::gammaPDerivative, "igamma_med_data_p_derivative.csv", 5, 1.25),
347         /** gamma p derivative computed for igamma Boost big data. */
348         GAMMA_P_DERIV_BIG(BoostGamma::gammaPDerivative, "igamma_big_data_p_derivative.csv", 550, 55),
349         /** gamma p derivative computed for igamma Boost int data. */
350         LOG_GAMMA_P_DERIV1_INT(BoostGammaTest::logGammaPDerivative1, "igamma_int_data_p_derivative.csv", 3, 50, 10),
351         /** gamma p derivative computed for igamma Boost small data. */
352         LOG_GAMMA_P_DERIV1_SMALL(BoostGammaTest::logGammaPDerivative1, "igamma_small_data_p_derivative.csv", 3, 8e11, 5e10),
353         /** gamma p derivative computed for igamma Boost med data. */
354         LOG_GAMMA_P_DERIV1_MED(BoostGammaTest::logGammaPDerivative1, "igamma_med_data_p_derivative.csv", 3, 190, 35),
355         /** gamma p derivative computed for igamma Boost big data. */
356         LOG_GAMMA_P_DERIV1_BIG(BoostGammaTest::logGammaPDerivative1, "igamma_big_data_p_derivative.csv", 3, 1.2e6, 125000),
357         /** gamma p derivative computed for igamma Boost int data. */
358         LOG_GAMMA_P_DERIV2_INT(BoostGammaTest::logGammaPDerivative2, "igamma_int_data_p_derivative.csv", 3, 2, 0.5),
359         /** gamma p derivative computed for igamma Boost small data. */
360         LOG_GAMMA_P_DERIV2_SMALL(BoostGammaTest::logGammaPDerivative2, "igamma_small_data_p_derivative.csv", 3, 1.8e10, 1.4e9),
361         /** gamma p derivative computed for igamma Boost med data. */
362         LOG_GAMMA_P_DERIV2_MED(BoostGammaTest::logGammaPDerivative2, "igamma_med_data_p_derivative.csv", 3, 18, 0.8),
363         /** gamma p derivative computed for igamma Boost big data. */
364         LOG_GAMMA_P_DERIV2_BIG(BoostGammaTest::logGammaPDerivative2, "igamma_big_data_p_derivative.csv", 3, 40000, 3000),
365         /** igamma asymptotic approximation term. */
366         IGAMMA_LARGE_X_ASYMP_TERM(BoostGammaTest::incompleteTgammaLargeX, "igamma_asymptotic_data.csv", 300, 110),
367         /** gamma Q where the asymptotic approximation applies and <em>is not</em> used. */
368         IGAMMA_LARGE_X_Q(BoostGammaTest::igammaQLargeXNoAsym, "igamma_asymptotic_data.csv", 3, 550, 130),
369         /** gamma P where the asymptotic approximation applies and <em>is not</em> used. */
370         IGAMMA_LARGE_X_P(BoostGammaTest::igammaPLargeXNoAsym, "igamma_asymptotic_data.csv", 4, 1, 0.3),
371         /** gamma Q where the asymptotic approximation applies and <em>is</em> used. */
372         IGAMMA_LARGE_X_Q_ASYM(BoostGammaTest::igammaQLargeXWithAsym, "igamma_asymptotic_data.csv", 3, 550, 190),
373         /** gamma P where the asymptotic approximation applies and <em>is</em> used. */
374         IGAMMA_LARGE_X_P_ASYM(BoostGammaTest::igammaPLargeXWithAsym, "igamma_asymptotic_data.csv", 4, 350, 110),
375         /** tgamma delta ratio Boost data. */
376         TGAMMA_DELTA_RATIO(BoostGamma::tgammaDeltaRatio, "gamma_delta_ratio_data.csv", 2, 9.5, 2.1),
377         /** tgamma delta ratio Boost small int data. */
378         TGAMMA_DELTA_RATIO_SMALL_INT(BoostGamma::tgammaDeltaRatio, "gamma_delta_ratio_int_data.csv", 2, 4.7, 1.1),
379         /** tgamma delta ratio Boost int data. */
380         TGAMMA_DELTA_RATIO_INT(BoostGamma::tgammaDeltaRatio, "gamma_delta_ratio_int2_data.csv", 2, 1.4, 0.45),
381         /** tgamma delta ratio Boost data. */
382         TGAMMA_DELTA_RATIO_NEG(BoostGammaTest::tgammaDeltaRatioNegative, "gamma_delta_ratio_data.csv", 3, 11.5, 1.7),
383         /** tgamma delta ratio Boost small int data. */
384         TGAMMA_DELTA_RATIO_SMALL_INT_NEG(BoostGammaTest::tgammaDeltaRatioNegative, "gamma_delta_ratio_int_data.csv", 3, 3.6, 1),
385         /** tgamma delta ratio Boost int data. */
386         TGAMMA_DELTA_RATIO_INT_NEG(BoostGammaTest::tgammaDeltaRatioNegative, "gamma_delta_ratio_int2_data.csv", 3, 1.1, 0.25),
387         /** tgamma ratio Boost data. */
388         TGAMMA_RATIO(BoostGamma::tgammaDeltaRatio, "gamma_delta_ratio_data.csv", 9.5, 2);
389 
390         /** The function. */
391         private final DoubleBinaryOperator fun;
392 
393         /** The filename containing the test data. */
394         private final String filename;
395 
396         /** The field containing the expected value. */
397         private final int expected;
398 
399         /** The maximum allowed ulp. */
400         private final double maxUlp;
401 
402         /** The maximum allowed RMS ulp. */
403         private final double rmsUlp;
404 
405         /**
406          * Instantiates a new test case.
407          *
408          * @param fun function to test
409          * @param filename Filename of the test data
410          * @param maxUlp maximum allowed ulp
411          * @param rmsUlp maximum allowed RMS ulp
412          */
413         BiTestCase(DoubleBinaryOperator fun, String filename, double maxUlp, double rmsUlp) {
414             this(fun, filename, 2, maxUlp, rmsUlp);
415         }
416 
417         /**
418          * Instantiates a new test case.
419          *
420          * @param fun function to test
421          * @param filename Filename of the test data
422          * @param expected Expected result field index
423          * @param maxUlp maximum allowed ulp
424          * @param rmsUlp maximum allowed RMS ulp
425          */
426         BiTestCase(DoubleBinaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
427             this.fun = fun;
428             this.filename = filename;
429             this.expected = expected;
430             this.maxUlp = maxUlp;
431             this.rmsUlp = rmsUlp;
432         }
433 
434         /**
435          * @return function to test
436          */
437         DoubleBinaryOperator getFunction() {
438             return fun;
439         }
440 
441         /**
442          * @return Filename of the test data
443          */
444         String getFilename() {
445             return filename;
446         }
447 
448         /**
449          * @return Expected result field index
450          */
451         int getExpectedField() {
452             return expected;
453         }
454 
455         @Override
456         public double getTolerance() {
457             return maxUlp;
458         }
459 
460         @Override
461         public double getRmsTolerance() {
462             return rmsUlp;
463         }
464     }
465 
466     @ParameterizedTest
467     @CsvSource({
468         // Pole errors
469         "0, NaN",
470         "-1, NaN",
471         "-2, NaN",
472         // Factorials: gamma(n+1) = n!
473         "1, 1",
474         "2, 1",
475         "3, 2",
476         "4, 6",
477         "5, 24",
478         "171, 0.7257415615307998967396728211129263114717e307",
479         "171.9, Infinity",
480         "172, Infinity",
481         "172.1, Infinity",
482         "500.34, Infinity, 0",
483         "1000.123, Infinity, 0",
484         "2000.1, Infinity, 0",
485         // Overflow close to negative poles creates signed zeros
486         "-171.99999999999997, 0.0",
487         "-172, NaN",
488         "-172.00000000000003, -0.0",
489         "-172.99999999999997, -0.0",
490         "-173, NaN",
491         "-173.00000000000003, 0.0",
492     })
493     void testTGammaEdgeCases(double z, double expected) {
494         Assertions.assertEquals(expected, BoostGamma.tgamma(z));
495     }
496 
497     /**
498      * Test the approach to the overflow limit of gamma(z).
499      * This checks that extreme edge case handling is correct.
500      */
501     @ParameterizedTest
502     @CsvSource({
503         // Values computed using boost long double implementation.
504         "171.5, 9.483367566824799e+307, 1",
505         "171.62, 1.7576826789978127e+308, 1",
506         "171.624, 1.7942117599248106e+308, 4",
507         "171.62437600000001, 1.7976842943982607e+308, 4",
508         "171.6244, Infinity, 0",
509     })
510     void testTGammaLimit(double z, double expected, int ulp) {
511         TestUtils.assertEquals(expected, BoostGamma.tgamma(z), ulp);
512     }
513 
514     /**
515      * tgamma spot tests extracted from
516      * {@code boost/libs/math/test/test_gamma.hpp}.
517      */
518     @Test
519     void testTGammaSpotTests() {
520         final int tolerance = 50;
521         assertClose(BoostGamma::tgamma, 3.5, 3.3233509704478425511840640312646472177454052302295, tolerance);
522         assertClose(BoostGamma::tgamma, 0.125, 7.5339415987976119046992298412151336246104195881491, tolerance);
523         assertClose(BoostGamma::tgamma, -0.125, -8.7172188593831756100190140408231437691829605421405, tolerance);
524         assertClose(BoostGamma::tgamma, -3.125, 1.1668538708507675587790157356605097019141636072094, tolerance);
525         // Lower tolerance on this one, is only really needed on Linux x86 systems, result is mostly down to std lib accuracy:
526         assertClose(BoostGamma::tgamma, -53249.0 / 1024, -1.2646559519067605488251406578743995122462767733517e-65, tolerance * 3);
527 
528         // Very small values, from a bug report by Rocco Romeo:
529         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -12), 4095.42302574977164107280305038926932586783813167844235368772, tolerance);
530         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -14), 16383.4228446989052821887834066513143241996925504706815681204, tolerance * 2);
531         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -25), 3.35544314227843645746319656372890833248893111091576093784981e7, tolerance);
532         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -27), 1.34217727422784342467508497080056807355928046680073490038257e8, tolerance);
533         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -29), 5.36870911422784336940727488260481582524683632281496706906706e8, tolerance);
534         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -35), 3.43597383674227843351272524573929605605651956475300480712955e10, tolerance);
535         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -54), 1.80143985094819834227843350984671942971248427509141008005685e16, tolerance);
536         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -64), 1.84467440737095516154227843350984671394471047428598176073616e19, tolerance);
537         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -66), 7.37869762948382064634227843350984671394068921181531525785592922800e19, tolerance);
538         assertClose(BoostGamma::tgamma, Math.scalb(1.0, -33), 8.58993459142278433521360841138215453639282914047157884932317481977e9, tolerance);
539         assertClose(BoostGamma::tgamma, 4 / Double.MAX_VALUE, Double.MAX_VALUE / 4, tolerance);
540         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -12), -4096.57745718775464971331294488248972086965434176847741450728, tolerance);
541         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -14), -16384.5772760354695939336148831283410381037202353359487504624, tolerance * 2);
542         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -25), -3.35544325772156943776992988569766723938420508937071533029983e7, tolerance);
543         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -27), -1.34217728577215672270574319043497450577151370942651414968627e8, tolerance);
544         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -29), -5.36870912577215666743793215770406791630514293641886249382012e8, tolerance);
545         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -34), -1.71798691845772156649591034966100693794360502123447124928244e10, tolerance);
546         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -54), -1.80143985094819845772156649015329155101490229157245556564920e16, tolerance);
547         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -64), -1.84467440737095516165772156649015328606601289230246224694513e19, tolerance);
548         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -66), -7.37869762948382064645772156649015328606199162983179574406439e19, tolerance);
549         assertClose(BoostGamma::tgamma, -Math.scalb(1.0, -33), -8.58993459257721566501667413261977598620193488449233402857632e9, tolerance);
550         assertClose(BoostGamma::tgamma, -4 / Double.MAX_VALUE, -Double.MAX_VALUE / 4, tolerance);
551         assertClose(BoostGamma::tgamma, -1 + Math.scalb(1.0, -22), -4.19430442278467170746130758391572421252211886167956799318843e6, tolerance);
552         assertClose(BoostGamma::tgamma, -1 - Math.scalb(1.0, -22), 4.19430357721600151046968956086404748206205391186399889108944e6, tolerance);
553         assertClose(BoostGamma::tgamma, -4 + Math.scalb(1.0, -20), 43690.7294216755534842491085530510391932288379640970386378756, tolerance);
554         assertClose(BoostGamma::tgamma, -4 - Math.scalb(1.0, -20), -43690.6039118698506165317137699180871126338425941292693705533, tolerance);
555         assertClose(BoostGamma::tgamma, -1 + Math.scalb(1.0, -44), -1.75921860444164227843350985473932247549232492467032584051825e13, tolerance);
556         assertClose(BoostGamma::tgamma, -1 - Math.scalb(1.0, -44), 1.75921860444155772156649016131144377791001546933519242218430e13, tolerance);
557         assertClose(BoostGamma::tgamma, -4 + Math.scalb(1.0, -44), 7.33007751850729421569517998006564998020333048893618664936994e11, tolerance);
558         assertClose(BoostGamma::tgamma, -4 - Math.scalb(1.0, -44), -7.33007751850603911763815347967171096249288790373790093559568e11, tolerance);
559         // Test bug fixes in tgamma:
560         assertClose(BoostGamma::tgamma, 142.75, 7.8029496083318133344429227511387928576820621466e244, tolerance * 4);
561         assertClose(BoostGamma::tgamma, -Double.MIN_VALUE, Double.NEGATIVE_INFINITY, 0);
562         assertClose(BoostGamma::tgamma, Double.MIN_VALUE, Double.POSITIVE_INFINITY, 0);
563     }
564 
565     @Test
566     void testLanczosGmh() {
567         // These terms are equal. The value (g - 0.5) is provided as a convenience.
568         Assertions.assertEquals(Lanczos.G - 0.5, Lanczos.GMH);
569         Assertions.assertEquals(0.5 - Lanczos.G, -Lanczos.GMH);
570     }
571 
572     @ParameterizedTest
573     @EnumSource(value = TestCase.class, mode = Mode.MATCH_ANY, names = {"TGAMMAO_.*"})
574     void testTGammaOriginal(TestCase tc) {
575         assertFunction(tc);
576     }
577 
578     @ParameterizedTest
579     @EnumSource(value = TestCase.class, mode = Mode.MATCH_ANY, names = {"TGAMMA_.*"})
580     void testTGamma(TestCase tc) {
581         assertFunction(tc);
582     }
583 
584     /**
585      * Read the gamma data and compare the result with the Commons Numbers 1.0 implementation
586      * using data that requires the Lanczos support. This test verifies the Boost Lanczos
587      * support is an improvement.
588      */
589     @ParameterizedTest
590     @ValueSource(strings = {"gamma_20_150_data.csv", "gamma_factorials_data.csv"})
591     @Order(1)
592     void testTGammaWithLanczosSupport(String datafile) throws Exception {
593         final TestUtils.ErrorStatistics e1 = new TestUtils.ErrorStatistics();
594         final TestUtils.ErrorStatistics e2 = new TestUtils.ErrorStatistics();
595 
596         // Set this to allow all data to be processed.
597         // If set to negative any failures will be output to stdout (useful to debugging).
598         final double tolerance = 10;
599 
600         try (DataReader in = new DataReader(datafile)) {
601             while (in.next()) {
602                 final double x = in.getDouble(0);
603 
604                 // The Boost method tabulates the integer factorials so skip these.
605                 // Also skip those not support by the partial implementation.
606                 if ((int) x == x || x <= LANCZOS_THRESHOLD) {
607                     continue;
608                 }
609 
610                 final double v1 = gammaOriginal(x);
611                 // The original method can overflow even when x <= 170 (the largest factorial).
612                 // The largest supported value is around 141.5.
613                 if (Double.isInfinite(v1)) {
614                     continue;
615                 }
616                 final double v2 = BoostGamma.tgamma(x);
617 
618                 final BigDecimal expected = in.getBigDecimal(1);
619 
620                 TestUtils.assertEquals(expected, v1, tolerance, e1::add, () -> "Numbers 1.0 x=" + x);
621                 TestUtils.assertEquals(expected, v2, tolerance, e2::add, () -> "Boost x=" + x);
622             }
623         }
624         // The gamma functions are accurate to a few ULP.
625         // This test is mainly interested in checking the Boost implementation is an improvement.
626         final double maxTolerance = 6;
627         final double rmsTolerance = 2;
628         assertRms(TestError.of(datafile + "  Numbers 1.0", maxTolerance, rmsTolerance), e1);
629         assertRms(TestError.of(datafile + "  Boost      ", maxTolerance, rmsTolerance), e2);
630         Assertions.assertTrue(e2.getRMS() < e1.getRMS() * 0.8, "Expected better precision");
631         Assertions.assertTrue(e2.getMaxAbs() < e1.getMaxAbs() * 0.7, "Expected lower max error");
632         Assertions.assertTrue(Math.abs(e2.getMean()) < Math.abs(e1.getMean()) * 0.5, "Expected better accuracy");
633     }
634 
635     @ParameterizedTest
636     @CsvSource({
637         // Pole errors
638         "0, NaN",
639         "-1, NaN",
640         "-2, NaN",
641         // Factorials: gamma(n+1) = n!
642         "1, 0",
643         "2, 0",
644     })
645     void testLGammaEdgeCases(double z, double p) {
646         Assertions.assertEquals(p, BoostGamma.lgamma(z));
647     }
648 
649     /**
650      * lgamma spot tests extracted from
651      * {@code boost/libs/math/test/test_gamma.hpp}.
652      */
653     @Test
654     void testLGammaSpotTests() {
655         final int tolerance = 1;
656         final int[] sign = {0};
657         final DoubleUnaryOperator fun = z -> BoostGamma.lgamma(z, sign);
658 
659         assertClose(fun, 3.5, 1.2009736023470742248160218814507129957702389154682, tolerance);
660         Assertions.assertEquals(1, sign[0]);
661         assertClose(fun, 0.125, 2.0194183575537963453202905211670995899482809521344, tolerance);
662         Assertions.assertEquals(1, sign[0]);
663         assertClose(fun, -0.125, 2.1653002489051702517540619481440174064962195287626, tolerance);
664         Assertions.assertEquals(-1, sign[0]);
665         assertClose(fun, -3.125, 0.1543111276840418242676072830970532952413339012367, tolerance * 2);
666         Assertions.assertEquals(1, sign[0]);
667         assertClose(fun, -53249.0 / 1024, -149.43323093420259741100038126078721302600128285894, tolerance);
668         Assertions.assertEquals(-1, sign[0]);
669         // Very small values, from a bug report by Rocco Romeo:
670         assertClose(fun, Math.scalb(1.0, -12), Math.log(4095.42302574977164107280305038926932586783813167844235368772), tolerance);
671         Assertions.assertEquals(1, sign[0]);
672         assertClose(fun, Math.scalb(1.0, -14), Math.log(16383.4228446989052821887834066513143241996925504706815681204), tolerance);
673         Assertions.assertEquals(1, sign[0]);
674         assertClose(fun, Math.scalb(1.0, -25), Math.log(3.35544314227843645746319656372890833248893111091576093784981e7), tolerance);
675         Assertions.assertEquals(1, sign[0]);
676         assertClose(fun, Math.scalb(1.0, -27), Math.log(1.34217727422784342467508497080056807355928046680073490038257e8), tolerance);
677         Assertions.assertEquals(1, sign[0]);
678         assertClose(fun, Math.scalb(1.0, -29), Math.log(5.36870911422784336940727488260481582524683632281496706906706e8), tolerance);
679         Assertions.assertEquals(1, sign[0]);
680         assertClose(fun, Math.scalb(1.0, -35), Math.log(3.43597383674227843351272524573929605605651956475300480712955e10), tolerance);
681         Assertions.assertEquals(1, sign[0]);
682         assertClose(fun, Math.scalb(1.0, -54), Math.log(1.80143985094819834227843350984671942971248427509141008005685e16), tolerance);
683         Assertions.assertEquals(1, sign[0]);
684         assertClose(fun, Math.scalb(1.0, -64), Math.log(1.84467440737095516154227843350984671394471047428598176073616e19), tolerance);
685         Assertions.assertEquals(1, sign[0]);
686         assertClose(fun, Math.scalb(1.0, -66), Math.log(7.37869762948382064634227843350984671394068921181531525785592922800e19), tolerance);
687         Assertions.assertEquals(1, sign[0]);
688         assertClose(fun, Math.scalb(1.0, -33), Math.log(8.58993459142278433521360841138215453639282914047157884932317481977e9), tolerance);
689         Assertions.assertEquals(1, sign[0]);
690         assertClose(fun, 4 / Double.MAX_VALUE, Math.log(Double.MAX_VALUE / 4), tolerance);
691         Assertions.assertEquals(1, sign[0]);
692         assertClose(fun, -Math.scalb(1.0, -12), Math.log(4096.57745718775464971331294488248972086965434176847741450728), tolerance);
693         Assertions.assertEquals(-1, sign[0]);
694         assertClose(fun, -Math.scalb(1.0, -14), Math.log(16384.5772760354695939336148831283410381037202353359487504624), tolerance);
695         Assertions.assertEquals(-1, sign[0]);
696         assertClose(fun, -Math.scalb(1.0, -25), Math.log(3.35544325772156943776992988569766723938420508937071533029983e7), tolerance);
697         Assertions.assertEquals(-1, sign[0]);
698         assertClose(fun, -Math.scalb(1.0, -27), Math.log(1.34217728577215672270574319043497450577151370942651414968627e8), tolerance);
699         Assertions.assertEquals(-1, sign[0]);
700         assertClose(fun, -Math.scalb(1.0, -29), Math.log(5.36870912577215666743793215770406791630514293641886249382012e8), tolerance);
701         Assertions.assertEquals(-1, sign[0]);
702         assertClose(fun, -Math.scalb(1.0, -34), Math.log(1.71798691845772156649591034966100693794360502123447124928244e10), tolerance);
703         Assertions.assertEquals(-1, sign[0]);
704         assertClose(fun, -Math.scalb(1.0, -54), Math.log(1.80143985094819845772156649015329155101490229157245556564920e16), tolerance);
705         Assertions.assertEquals(-1, sign[0]);
706         assertClose(fun, -Math.scalb(1.0, -64), Math.log(1.84467440737095516165772156649015328606601289230246224694513e19), tolerance);
707         Assertions.assertEquals(-1, sign[0]);
708         assertClose(fun, -Math.scalb(1.0, -66), Math.log(7.37869762948382064645772156649015328606199162983179574406439e19), tolerance);
709         Assertions.assertEquals(-1, sign[0]);
710         assertClose(fun, -Math.scalb(1.0, -33), Math.log(8.58993459257721566501667413261977598620193488449233402857632e9), tolerance);
711         Assertions.assertEquals(-1, sign[0]);
712         assertClose(fun, -4 / Double.MAX_VALUE, Math.log(Double.MAX_VALUE / 4), tolerance);
713         Assertions.assertEquals(-1, sign[0]);
714         assertClose(fun, -1 + Math.scalb(1.0, -22), Math.log(4.19430442278467170746130758391572421252211886167956799318843e6), tolerance);
715         Assertions.assertEquals(-1, sign[0]);
716         assertClose(fun, -1 - Math.scalb(1.0, -22), Math.log(4.19430357721600151046968956086404748206205391186399889108944e6), tolerance);
717         Assertions.assertEquals(1, sign[0]);
718         assertClose(fun, -4 + Math.scalb(1.0, -20), Math.log(43690.7294216755534842491085530510391932288379640970386378756), tolerance);
719         Assertions.assertEquals(1, sign[0]);
720         assertClose(fun, -4 - Math.scalb(1.0, -20), Math.log(43690.6039118698506165317137699180871126338425941292693705533), tolerance);
721         Assertions.assertEquals(-1, sign[0]);
722 
723         assertClose(fun, -1 + Math.scalb(1.0, -44), Math.log(1.75921860444164227843350985473932247549232492467032584051825e13), tolerance);
724         Assertions.assertEquals(-1, sign[0]);
725         assertClose(fun, -1 - Math.scalb(1.0, -44), Math.log(1.75921860444155772156649016131144377791001546933519242218430e13), tolerance);
726         Assertions.assertEquals(1, sign[0]);
727         assertClose(fun, -4 + Math.scalb(1.0, -44), Math.log(7.33007751850729421569517998006564998020333048893618664936994e11), tolerance);
728         Assertions.assertEquals(1, sign[0]);
729         assertClose(fun, -4 - Math.scalb(1.0, -44), Math.log(7.33007751850603911763815347967171096249288790373790093559568e11), tolerance);
730         Assertions.assertEquals(-1, sign[0]);
731         //
732         // Extra large values for lgamma, see https://github.com/boostorg/math/issues/242
733         //
734         assertClose(fun, Math.scalb(11103367432951928.0, 32), 2.7719825960021351251696385101478518546793793286704974382373670822285114741208958e27, tolerance);
735         assertClose(fun, Math.scalb(11103367432951928.0, 62), 4.0411767712186990905102512019058204792570873633363159e36, tolerance);
736         assertClose(fun, Math.scalb(11103367432951928.0, 326), 3.9754720509185529233002820161357111676582583112671658e116, tolerance);
737         //
738         // Super small values may cause spurious overflow:
739         //
740         double value = Double.MIN_NORMAL;
741         while (value != 0) {
742             Assertions.assertTrue(Double.isFinite(fun.applyAsDouble(value)));
743             value /= 2;
744         }
745 
746         // Simple check to ensure a zero length array is ignored
747         final int[] signEmpty = {};
748         for (final double z : new double[] {3.5, 6.76, 8.12}) {
749             Assertions.assertEquals(BoostGamma.lgamma(z), BoostGamma.lgamma(z, signEmpty));
750         }
751     }
752 
753     @ParameterizedTest
754     @EnumSource(value = TestCase.class, mode = Mode.MATCH_ANY, names = {"LGAMMA_.*"})
755     void testLGamma(TestCase tc) {
756         assertFunction(tc);
757     }
758 
759     @ParameterizedTest
760     @CsvSource({
761         // Pole errors
762         "-1, NaN",
763         "-2, NaN",
764         // Factorials: gamma(n+1)-1 = n! - 1
765         "0, 0",
766         "1, 0",
767         "2, 1",
768         "3, 5",
769         "4, 23",
770         "5, 119",
771     })
772     void testTGammap1m1EdgeCases(double z, double p) {
773         Assertions.assertEquals(p, BoostGamma.tgamma1pm1(z));
774     }
775 
776     @ParameterizedTest
777     @EnumSource(value = TestCase.class, mode = Mode.MATCH_ANY, names = {"TGAMMAP1M1.*"})
778     void testTGammap1m1(TestCase tc) {
779         assertFunction(tc);
780     }
781 
782     @ParameterizedTest
783     @CsvSource({
784         "0, 1, -1",
785         "0, 0, 0",
786         "0, -1, Infinity",
787         "2, -2, -0.75",
788         "2, 1024, Infinity",
789         "2, -1075, -1",
790         "NaN, 1, NaN",
791         "1, NaN, NaN",
792         // Negative x, even integer y
793         "-2, 2, 3",
794         // Negative x, non (even integer) y
795         "-2, 2.1, NaN",
796     })
797     void testPowm1EdgeCases(double x, double y, double expected) {
798         Assertions.assertEquals(expected, BoostMath.powm1(x, y));
799     }
800 
801     @ParameterizedTest
802     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"POWM1.*"})
803     void testPowm1(BiTestCase tc) {
804         assertFunction(tc);
805     }
806 
807     /**
808      * Test the log1pmx function with values that do not require high precision.
809      *
810      * @param x Argument x
811      */
812     @ParameterizedTest
813     @ValueSource(doubles = {-1.1, -1, -0.9, 0, 1, 1.5, 2, 3})
814     void testLog1pmxStandard(double x) {
815         Assertions.assertEquals(Math.log1p(x) - x, SpecialMath.log1pmx(x));
816     }
817 
818     /**
819      * Test the log1pmx function. The function is not a direct port of the Boost log1pmx
820      * function so resides in the {@link SpecialMath} class. It is only used in {@link BoostGamma}
821      * so tested here using the same test framework.
822      */
823     @ParameterizedTest
824     @EnumSource(value = TestCase.class, mode = Mode.MATCH_ANY, names = {"LOG1PMX.*"})
825     void testLog1pmx(TestCase tc) {
826         assertFunction(tc);
827     }
828 
829     @ParameterizedTest
830     @CsvSource({
831         // Argument a > 0
832         "NaN, 1, NaN",
833         "0, 1, NaN",
834         "-1, 1, NaN",
835         // Argument z >= 0
836         "1, NaN, NaN",
837         "1, -1, NaN",
838     })
839     void testIGammaEdgeCases(double a, double z, double expected) {
840         // All functions have the same invalid domain for a and z
841         Assertions.assertEquals(expected, BoostGamma.tgamma(a, z), "tgamma");
842         Assertions.assertEquals(expected, BoostGamma.tgammaLower(a, z), "tgammaLower");
843         Assertions.assertEquals(expected, BoostGamma.gammaP(a, z), "gammaP");
844         Assertions.assertEquals(expected, BoostGamma.gammaQ(a, z), "gammaQ");
845         Assertions.assertEquals(expected, BoostGamma.gammaPDerivative(a, z), "gammaPDerivative");
846     }
847 
848     @ParameterizedTest
849     @CsvSource({
850         // z==0
851         "2, 0, 0",
852         "1, 0, 1",
853         "0.5, 0, Infinity",
854     })
855     void testGammaPDerivativeEdgeCases(double a, double z, double expected) {
856         Assertions.assertEquals(expected, BoostGamma.gammaPDerivative(a, z));
857     }
858 
859     /**
860      * tgamma spot tests extracted from
861      * {@code boost/libs/math/test/test_igamma.hpp}.
862      */
863     @Test
864     void testIGammaSpotTests() {
865         int tolerance = 10;
866         assertClose(BoostGamma::tgamma, 5, 1, 23.912163676143750903709045060494956383977723517065, tolerance);
867         assertClose(BoostGamma::tgamma, 5, 5, 10.571838841565097874621959975919877646444998907920, tolerance);
868         assertClose(BoostGamma::tgamma, 5, 10, 0.70206451384706574414638719662835463671916532623256, tolerance);
869         assertClose(BoostGamma::tgamma, 5, 100, 3.8734332808745531496973774140085644548465762343719e-36, tolerance);
870         assertClose(BoostGamma::tgamma, 0.5, 0.5, 0.56241823159440712427949495730204306902676756479651, tolerance);
871         assertClose(BoostGamma::tgamma, 0.5, 9.0 / 10, 0.31853210360412109873859360390443790076576777747449, tolerance * 10);
872         assertClose(BoostGamma::tgamma, 0.5, 5, 0.0027746032604128093194908357272603294120210079791437, tolerance);
873         assertClose(BoostGamma::tgamma, 0.5, 100, 3.7017478604082789202535664481339075721362102520338e-45, tolerance);
874 
875         assertClose(BoostGamma::tgammaLower, 5, 1, 0.087836323856249096290954939505043616022276482935091, tolerance);
876         assertClose(BoostGamma::tgammaLower, 5, 5, 13.428161158434902125378040024080122353555001092080, tolerance);
877         assertClose(BoostGamma::tgammaLower, 5, 10, 23.297935486152934255853612803371645363280834673767, tolerance);
878         assertClose(BoostGamma::tgammaLower, 5, 100, 23.999999999999999999999999999999999996126566719125, tolerance);
879 
880         assertClose(BoostGamma::gammaQ, 5, 1, 0.99634015317265628765454354418728984933240514654437, tolerance);
881         assertClose(BoostGamma::gammaQ, 5, 5, 0.44049328506521241144258166566332823526854162116334, tolerance);
882         assertClose(BoostGamma::gammaQ, 5, 10, 0.029252688076961072672766133192848109863298555259690, tolerance);
883         assertClose(BoostGamma::gammaQ, 5, 100, 1.6139305336977304790405739225035685228527400976549e-37, tolerance);
884         assertClose(BoostGamma::gammaQ, 1.5, 2, 0.26146412994911062220282207597592120190281060919079, tolerance);
885         assertClose(BoostGamma::gammaQ, 20.5, 22, 0.34575332043467326814971590879658406632570278929072, tolerance);
886 
887         assertClose(BoostGamma::gammaP, 5, 1, 0.0036598468273437123454564558127101506675948534556288, tolerance);
888         assertClose(BoostGamma::gammaP, 5, 5, 0.55950671493478758855741833433667176473145837883666, tolerance);
889         assertClose(BoostGamma::gammaP, 5, 10, 0.97074731192303892732723386680715189013670144474031, tolerance);
890         assertClose(BoostGamma::gammaP, 5, 100, 0.9999999999999999999999999999999999998386069466302, tolerance);
891         assertClose(BoostGamma::gammaP, 1.5, 2, 0.73853587005088937779717792402407879809718939080921, tolerance);
892         assertClose(BoostGamma::gammaP, 20.5, 22, 0.65424667956532673185028409120341593367429721070928, tolerance);
893 
894         // naive check on derivative function:
895         tolerance = 50;
896         assertClose(BoostGamma::gammaPDerivative, 20.5, 22,
897             Math.exp(-22) * Math.pow(22, 19.5) / BoostGamma.tgamma(20.5), tolerance);
898 
899         // Bug reports from Rocco Romeo:
900         assertClose(BoostGamma::tgamma, 20, Math.scalb(1.0, -40), 1.21645100408832000000e17, tolerance);
901         assertClose(BoostGamma::tgammaLower, 20, Math.scalb(1.0, -40), 7.498484069471659696438206828760307317022658816757448882e-243, tolerance);
902         assertClose(BoostGamma::gammaP, 20, Math.scalb(1.0, -40), 6.164230243774976473534975936127139110276824507876192062e-260, tolerance);
903 
904         assertClose(BoostGamma::tgamma, 30, Math.scalb(1.0, -30), 8.841761993739701954543616000000e30, tolerance);
905         assertClose(BoostGamma::tgammaLower, 30, Math.scalb(1.0, -30), 3.943507283668378474979245322638092813837393749566146974e-273, tolerance);
906         assertClose(BoostGamma::gammaP, 30, Math.scalb(1.0, -30), 4.460092102072560946444018923090222645613009128135650652e-304, tolerance);
907         assertClose(BoostGamma::gammaPDerivative, 2, Math.scalb(1.0, -575), 8.08634922390438981326119906687585206568664784377654648227177e-174, tolerance);
908 
909         assertEquals(BoostGamma::tgamma, 176, 100, Double.POSITIVE_INFINITY);
910         assertEquals(BoostGamma::tgamma, 530, 2000, Double.POSITIVE_INFINITY);
911         assertEquals(BoostGamma::tgamma, 740, 2500, Double.POSITIVE_INFINITY);
912         assertEquals(BoostGamma::tgamma, 530.5, 2000, Double.POSITIVE_INFINITY);
913         assertEquals(BoostGamma::tgamma, 740.5, 2500, Double.POSITIVE_INFINITY);
914         assertEquals(BoostGamma::tgammaLower, 10000.0f, 10000.0f / 4, Double.POSITIVE_INFINITY);
915         assertClose(BoostGamma::tgamma, 170, 165, 2.737338337642022829223832094019477918166996032112404370e304, tolerance);
916         assertClose(BoostGamma::tgammaLower, 170, 165, 1.531729671362682445715419794880088619901822603944331733e304, tolerance);
917         // *** Increased from 10 * tolerance ***
918         assertClose(BoostGamma::tgamma, 170, 170, 2.090991698081449410761040647015858316167077909285580375e304, 16 * tolerance);
919         assertClose(BoostGamma::tgammaLower, 170, 170, 2.178076310923255864178211241883708221901740726771155728e304, 16 * tolerance);
920         assertClose(BoostGamma::tgamma, 170, 190, 2.8359275512790301602903689596273175148895758522893941392e303, 10 * tolerance);
921         assertClose(BoostGamma::tgammaLower, 170, 190, 3.985475253876802258910214992936834786579861050827796689e304, 10 * tolerance);
922         // *** Increased from 10 * tolerance ***
923         assertClose(BoostGamma::tgamma, 170, 1000, 6.1067635957780723069200425769800190368662985052038980542e72, 16 * tolerance);
924 
925         assertClose(BoostGamma::tgammaLower, 185, 1, 0.001999286058955490074702037576083582139834300307968257924836, tolerance);
926         assertClose(BoostGamma::tgamma, 185, 1500, 1.037189524841404054867100938934493979112615962865368623e-67, tolerance * 10);
927 
928         assertClose(BoostGamma::tgamma, 36, Math.scalb(1.0, -26), 1.03331479663861449296666513375232000000e40, tolerance * 10);
929         assertClose(BoostGamma::tgamma, 50.5, Math.scalb(1.0, -17), 4.2904629123519598109157551960589377e63, tolerance * 10);
930         assertClose(BoostGamma::tgamma, 164.5, 0.125, 2.5649307433687542701168405519538910e292, tolerance * 10);
931         //
932         // Check very large parameters, see: https://github.com/boostorg/math/issues/168
933         //
934         final double maxVal = Double.MAX_VALUE;
935         final double largeVal = maxVal * 0.99f;
936         assertEquals(BoostGamma::tgamma, 22.25, maxVal, 0);
937         assertEquals(BoostGamma::tgamma, 22.25, largeVal, 0);
938         assertEquals(BoostGamma::tgammaLower, 22.25, maxVal, BoostGamma.tgamma(22.25));
939         assertEquals(BoostGamma::tgammaLower, 22.25, largeVal, BoostGamma.tgamma(22.25));
940         assertEquals(BoostGamma::gammaQ, 22.25, maxVal, 0);
941         assertEquals(BoostGamma::gammaQ, 22.25, largeVal, 0);
942         assertEquals(BoostGamma::gammaP, 22.25, maxVal, 1);
943         assertEquals(BoostGamma::gammaP, 22.25, largeVal, 1);
944         assertEquals(BoostGamma::tgamma, 22.25, Double.POSITIVE_INFINITY, 0);
945         assertEquals(BoostGamma::tgammaLower, 22.25, Double.POSITIVE_INFINITY, BoostGamma.tgamma(22.25));
946         assertEquals(BoostGamma::gammaQ, 22.25, Double.POSITIVE_INFINITY, 0);
947         assertEquals(BoostGamma::gammaP, 22.25, Double.POSITIVE_INFINITY, 1);
948         //
949         // Large arguments and small parameters, see
950         // https://github.com/boostorg/math/issues/451:
951         //
952         assertEquals(BoostGamma::gammaQ, 1770, 1e-12, 1);
953         assertEquals(BoostGamma::gammaP, 1770, 1e-12, 0);
954     }
955 
956     @ParameterizedTest
957     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"IGAMMA_U.*"})
958     void testIGammaUpper(BiTestCase tc) {
959         assertFunction(tc);
960     }
961 
962     @ParameterizedTest
963     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"IGAMMA_L.*"})
964     void testIGammaLower(BiTestCase tc) {
965         assertFunction(tc);
966     }
967 
968     @ParameterizedTest
969     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"IGAMMA_Q.*"})
970     void testIGammaQ(BiTestCase tc) {
971         assertFunction(tc);
972     }
973 
974     @ParameterizedTest
975     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"IGAMMA_P.*"})
976     void testIGammaP(BiTestCase tc) {
977         assertFunction(tc);
978     }
979 
980     @ParameterizedTest
981     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"GAMMA_P_DERIV.*"})
982     void testGammaPDerivative(BiTestCase tc) {
983         assertFunction(tc);
984     }
985 
986     @ParameterizedTest
987     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"LOG_GAMMA_P_DERIV.*"})
988     void testLogGammaPDerivative(BiTestCase tc) {
989         assertFunction(tc);
990     }
991 
992     /**
993      * Test the incomplete gamma function uses the policy containing the epsilon and
994      * maximum iterations for series evaluations. The data targets each method computed
995      * using a series component to check the policy is not ignored.
996      *
997      * <p>Running the policy tests on their own should hit the code paths
998      * using the policy for function evaluation:
999      * <pre>
1000      * mvn clean test -Dtest=BoostGammaTest#testIGammaPolicy* jacoco:report
1001      * </pre>
1002      */
1003     @ParameterizedTest
1004     @CsvSource(value = {
1005         // Methods 0, 1, 5, 6 do not use the iteration policy
1006         // Data extracted from the resource files and formatted to double precision
1007 
1008         // Method 2: x > 1.1, x - (1 / (3 * x)) < a
1009         "5.0,2.5,21.38827245393963,0.8911780189141513,2.6117275460603704,0.10882198108584876",
1010         // Method 4: a < 20, x > 1.1, x - (1 / (3 * x)) > a
1011         "19.24400520324707,21.168405532836914,4.0308280447358675E15,0.3084240508178698,9.038282597080282E15,0.6915759491821302",
1012         // Method 7: (x > 1000) && (a < x * 0.75f)
1013         "664.0791015625,1328.158203125,Infinity,4.90100553385586E-91,Infinity,1.0",
1014         // Method 2: 0.5 < x < 1.1, x * 0.75f < a
1015         "0.9759566783905029,1.0735523700714111,0.33659577343416824,0.33179703084688433,0.6778671124302277,0.6682029691531157",
1016         // Method 3: 0.5 < x < 1.1, x * 0.75f > a
1017         "0.4912221431732178,0.9824442863464355,0.2840949896471149,0.1575143024618326,1.519518937513272,0.8424856975381674",
1018     })
1019     void testIGammaPolicy(double a, double x, double upper, double q, double lower, double p) {
1020         // Low iterations should fail to converge
1021         final Policy pol1 = new Policy(0x1.0p-52, 1);
1022         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.tgamma(a, x, pol1), "upper");
1023         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.tgammaLower(a, x, pol1), "lower");
1024         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.gammaP(a, x, pol1), "p");
1025         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.gammaQ(a, x, pol1), "q");
1026 
1027         // Low epsilon should not be as accurate
1028         final Policy pol2 = new Policy(1e-3, Integer.MAX_VALUE);
1029 
1030         // Innore infinite
1031         if (Double.isFinite(upper)) {
1032             final double u1 = BoostGamma.tgamma(a, x);
1033             final double u2 = BoostGamma.tgamma(a, x, pol2);
1034             assertCloser("upper", upper, u1, u2);
1035         }
1036         if (Double.isFinite(lower)) {
1037             final double l1 = BoostGamma.tgammaLower(a, x);
1038             final double l2 = BoostGamma.tgammaLower(a, x, pol2);
1039             assertCloser("lower", lower, l1, l2);
1040         }
1041 
1042         // Ignore 0 or 1
1043         if ((int) p != p) {
1044             final double p1 = BoostGamma.gammaP(a, x);
1045             final double p2 = BoostGamma.gammaP(a, x, pol2);
1046             assertCloser("p", p, p1, p2);
1047         }
1048         if ((int) q != q) {
1049             final double q1 = BoostGamma.gammaQ(a, x);
1050             final double q2 = BoostGamma.gammaQ(a, x, pol2);
1051             assertCloser("q", q, q1, q2);
1052         }
1053     }
1054 
1055     @Test
1056     void testIGammaPolicy1() {
1057         // a >= MAX_FACTORIAL && !normalised; invert && (a * 4 < x)
1058         final double a = 230.1575469970703125;
1059         final double x = 23015.75390625;
1060         // expected ~ 0.95e-8996
1061         final double upper = 0;
1062         final double u1 = BoostGamma.tgamma(a, x);
1063         Assertions.assertEquals(upper, u1);
1064         final Policy pol1 = new Policy(0x1.0p-52, 1);
1065         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.tgamma(a, x, pol1), "upper");
1066         // Not possible to test the result is not as close to zero with a lower epsilon
1067     }
1068 
1069     @Test
1070     void testIGammaPolicy2() {
1071         // a >= MAX_FACTORIAL && !normalised; !invert && (a > 4 * x)
1072         final double a = 5823.5341796875;
1073         final double x = 1.0;
1074         final double lower = 0.6318201301512319242829e-4;
1075         final double l1 = BoostGamma.tgammaLower(a, x);
1076         Assertions.assertEquals(lower, l1, lower * 1e-10);
1077         final Policy pol1 = new Policy(0x1.0p-52, 1);
1078         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.tgammaLower(a, x, pol1), "lower");
1079         final Policy pol2 = new Policy(1e-3, Integer.MAX_VALUE);
1080         assertCloser("lower", lower, l1, BoostGamma.tgammaLower(a, x, pol2));
1081     }
1082 
1083     @Test
1084     void testIGammaPolicy3() {
1085         // a >= MAX_FACTORIAL && !normalised; other
1086         // In this case the regularized result is first computed then scaled back.
1087         // The regularized result is typically around 0.5 and is
1088         // computed without iteration with the Temme algorithm (method 5).
1089         // No cases exist in the current test data that require iteration.
1090         // We can check the iteration policy is used for one case.
1091         final double a = 53731.765625;
1092         final double x = 26865.8828125;
1093         final double lower = Double.POSITIVE_INFINITY;
1094         final double l1 = BoostGamma.tgammaLower(a, x);
1095         Assertions.assertEquals(lower, l1);
1096         final Policy pol1 = new Policy(0x1.0p-52, 1);
1097         Assertions.assertThrows(ArithmeticException.class, () -> BoostGamma.tgammaLower(a, x, pol1), "lower");
1098     }
1099 
1100     /**
1101      * Assert x is closer to the expected result than y.
1102      */
1103     private static void assertCloser(String msg, double expected, double x, double y) {
1104         final double dx = Math.abs(expected - x);
1105         final double dy = Math.abs(expected - y);
1106         Assertions.assertTrue(dx < dy,
1107             () -> String.format("%s %s : %s (%s) : %s (%s)", msg, expected, x, dx, y, dy));
1108     }
1109 
1110     /**
1111      * Test incomplete gamma function with large X. This is a subset of the test data
1112      * to isolate the use of the asymptotic approximation for the upper gamma fraction.
1113      */
1114     @ParameterizedTest
1115     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"IGAMMA_LARGE_X.*"})
1116     void testIGammaLargeX(BiTestCase tc) {
1117         assertFunction(tc);
1118     }
1119 
1120     @Test
1121     void testIGammaAsymptoticApproximationFailsOnVeryLargeX() {
1122         final double a = 1e30;
1123         final double x = a + Math.ulp(a);
1124         // The aymptotic approximation can be used as a < x:
1125         Assertions.assertTrue(a < x);
1126         // The term is too big to subtract small integers
1127         Assertions.assertEquals(a, a - 1);
1128 
1129         // This will not converge as the term will not reduce.
1130         // Limit the iterations to a million.
1131         final Policy pol = new Policy(Math.ulp(1.0), 10000000);
1132         Assertions.assertThrows(ArithmeticException.class,
1133             () -> BoostGamma.incompleteTgammaLargeX(a, x, pol));
1134     }
1135 
1136     /**
1137      * Test the Boost 1_77_0 switch to the asymptotic approximation results in
1138      * less accurate results for Q.
1139      *
1140      * <p>Note: @Order annotations are used on these methods to collect them together
1141      * if outputting the summary to stdout.
1142      */
1143     @ParameterizedTest
1144     @ValueSource(strings = {"igamma_int_data.csv", "igamma_med_data.csv", "igamma_big_data.csv"})
1145     @Order(1)
1146     void testGammaQLargeXOriginal(String datafile) throws Exception {
1147         assertIgammaLargeX("Boost", datafile, true, USE_ASYM_APPROX, USE_ASYM_APPROX, false);
1148     }
1149 
1150     /**
1151      * Test the Boost 1_77_0 switch to the asymptotic approximation results in
1152      * less accurate results for P.
1153      */
1154     @ParameterizedTest
1155     @ValueSource(strings = {"igamma_int_data.csv", "igamma_med_data.csv", "igamma_big_data.csv"})
1156     @Order(1)
1157     void testGammaPLargeXOriginal(String datafile) throws Exception {
1158         assertIgammaLargeX("Boost", datafile, false, USE_ASYM_APPROX, USE_ASYM_APPROX, false);
1159     }
1160 
1161     /**
1162      * Test an updated switch to the asymptotic approximation results in
1163      * more accurate results for Q.
1164      */
1165     @ParameterizedTest
1166     @ValueSource(strings = {"igamma_int_data.csv", "igamma_med_data.csv", "igamma_big_data.csv"})
1167     @Order(1)
1168     void testGammaQLargeX(String datafile) throws Exception {
1169         assertIgammaLargeX("Commons", datafile, true, getLargeXTarget(), getUseAsymApprox(), true);
1170     }
1171 
1172     /**
1173      * Test an updated switch to the asymptotic approximation results in
1174      * more accurate results for P.
1175      */
1176     @ParameterizedTest
1177     @ValueSource(strings = {"igamma_int_data.csv", "igamma_med_data.csv", "igamma_big_data.csv"})
1178     @Order(1)
1179     void testGammaPLargeX(String datafile) throws Exception {
1180         assertIgammaLargeX("Commons", datafile, false, getLargeXTarget(), getUseAsymApprox(), true);
1181     }
1182 
1183     /**
1184      * @return Predicate to identify target data for igamma suitable for the asymptotic approximation.
1185      */
1186     private static DoubleDoubleBiPredicate getLargeXTarget() {
1187         // Target the data that is computed using this method in Boost.
1188         // It will test that the method switch is an improvement.
1189         return USE_ASYM_APPROX;
1190 
1191         // Target all data that is computed using this method in Commons numbers.
1192         // It will test that when the method is switched it
1193         // will not make the computation worse.
1194         //return getUseAsymApprox();
1195 
1196         // Allow more data to be tested.
1197         // This predicate returns all valid data. Use it for testing but note that
1198         // most data is not suitable for the large x approximation.
1199         //return (a, x) -> !isIntOrHalfInt(a, x) && x >= 1.1 && (a < x);
1200     }
1201 
1202     /**
1203      * @return Predicate to identify when to use the asymptotic approximation.
1204      */
1205     private static DoubleDoubleBiPredicate getUseAsymApprox() {
1206         // The asymptotic approximation is suitable for large x.
1207         // It sums terms starting from 1 until convergence.
1208         // term_n = term_(n-1) * (a-n) / z
1209         // term_0 = 1
1210         // Terms will get smaller if a < z.
1211         // The Boost condition is:
1212         // (x > 1000) && ((a < x) || (Math.abs(a - 50) / x < 1))
1213         //
1214         // This is not suitable if a ~ x and x is very large. The series takes
1215         // too many iterations to converge. With limited precision it may not be
1216         // possible to reduce the size of a, e.g. 1e16 - 1 == 1e16, thus
1217         // the terms do not reduce in size if decremented directly and take
1218         // many iterations to build a counter n that can be subtracted: a - n
1219 
1220         // Experimental:
1221         // Estimate the number of iterations.
1222         //
1223         // Assuming the term is reduced by a/z:
1224         // term_n = (a/z)^n
1225         // Setting the limit for term_n == epsilon (2^-52):
1226         // n = log(epsilon) / log(a/z)
1227         // Note: log(2^-52) is approximately -36.
1228         // Estimate of n is high given the factor is (a-i)/z for iteration i.
1229 
1230         // int limit = 1000;
1231         // return (a, x) -> (x > 1000) && (a < x) && (-36 / Math.log(a / x) < limit);
1232 
1233         // Simple:
1234         //
1235         // Target only data that will quickly converge.
1236         // Not that there is loss of precision in the terms when a ~ z.
1237         // The closer a/z is to 1 the more bits are lost in the result as the
1238         // terms are inexact and this error is compounded by a large number of terms
1239         // in the series.
1240         // This condition ensures the asymptotic approximation is used when x -> infinity
1241         // and a << x.
1242         // Using the logic from above an overestimate of the maximum iterations is:
1243         // threshold   value      iterations
1244         // 1 - 2^-1    0.5        52
1245         // 1 - 2^-2    0.75       125
1246         // 1 - 2^-3    0.875      270
1247         // 1 - 2^-4    0.9375     558
1248         // 1 - 2^-5    0.96875    1135
1249         // 1 - 2^-6    0.984375   2289
1250         return (a, x) -> (x > 1000) && (a < x * 0.75);
1251     }
1252 
1253     /**
1254      * Read the regularized incomplete gamma data and compare the result with or
1255      * without the asymptotic approximation for large X. This test verifies the
1256      * conditions used for the asymptotic approximation.
1257      *
1258      * <p>All applicable target data is evaluated with the incomplete gamma function,
1259      * either without the asymptotic approximation or using it when allowed to
1260      * by the specified predicate. The final RMS error for the two methods
1261      * with or without the asymptotic approximation applied are compared. The test
1262      * asserts if the change is an improvement or not.
1263      *
1264      * <p>Prior to Boost 1_68_0 the asymptotic approximation was not used. It was
1265      * added to support large x. Using the conditions from Boost 1_77_0 to switch to
1266      * the asymptotic approximation results is worse accuracy.
1267      *
1268      * <p>This test can be revisited if the Boost reference code is updated for the
1269      * conditions under which the asymptotic approximation is used. It is possible
1270      * to adjust the condition directly in this test and determine if the RMS error
1271      * improves using the asymptotic approximation.
1272      *
1273      * @param name Test name
1274      * @param datafile Test data file
1275      * @param invert true to compute the upper value Q (default is lower P)
1276      * @param targetData Condition to identify the target data
1277      * @param useAsymp Condition to use the asymptotic approximation
1278      * @param expectImprovement true if the RMS error is expected to improve
1279      * @throws Exception If an error occurs reading the test files
1280      */
1281     private static void assertIgammaLargeX(String name, String datafile, boolean invert,
1282             DoubleDoubleBiPredicate targetData,
1283             DoubleDoubleBiPredicate useAsymp,
1284             boolean expectImprovement) throws Exception {
1285         final TestUtils.ErrorStatistics e1 = new TestUtils.ErrorStatistics();
1286         final TestUtils.ErrorStatistics e2 = new TestUtils.ErrorStatistics();
1287 
1288         final Policy pol = Policy.getDefault();
1289         final DoubleBinaryOperator without = (a, x) -> gammaIncompleteImp(a, x, invert, pol, NO_ASYM_APPROX);
1290         final DoubleBinaryOperator with = (a, x) -> gammaIncompleteImp(a, x, invert, pol, useAsymp);
1291         final int expectedField = invert ? 3 : 5;
1292 
1293         // Count how many times the asymptotic approximation was used
1294         int count = 0;
1295 
1296         final String functionName = datafile + " " + name + (invert ? " Q" : " P");
1297 
1298         // Set this to allow all data to be processed.
1299         // If set to negative any failures will be output to stdout (useful to debugging).
1300         final double tolerance = 1000;
1301 
1302         try (DataReader in = new DataReader(datafile)) {
1303             while (in.next()) {
1304                 final double a = in.getDouble(0);
1305                 final double x = in.getDouble(1);
1306 
1307                 // Test if this is target data
1308                 if (targetData.test(a, x)) {
1309                     final BigDecimal expected = in.getBigDecimal(expectedField);
1310 
1311                     // Ignore 0 or 1 results.
1312                     // This test is interested in values that can be computed.
1313                     final double value = expected.doubleValue();
1314                     if ((int) value != value) {
1315                         final double v1 = without.applyAsDouble(a, x);
1316                         final double v2 = with.applyAsDouble(a, x);
1317                         // Check if the asymptoptic approximation is used
1318                         if (useAsymp.test(a, x)) {
1319                             count++;
1320                         }
1321 
1322                         TestUtils.assertEquals(expected, v1, tolerance, e1::add,
1323                             () -> functionName + " " + a + ", x=" + x);
1324                         TestUtils.assertEquals(expected, v2, tolerance, e2::add,
1325                             () -> functionName + " asymp " + a + ", x=" + x);
1326                     }
1327                 }
1328             }
1329         }
1330         // Use relaxed tolerances to allow the big data to pass.
1331         // This test is mainly interested in checking a switch to the asymptotic approximation
1332         // does not make the computation worse.
1333         final double maxTolerance = 600;
1334         final double rmsTolerance = 600;
1335         if (e1.size() != 0) {
1336             assertRms(TestError.of(functionName + "           ", maxTolerance, rmsTolerance), e1);
1337             assertRms(TestError.of(functionName + " asymp " +
1338                     String.format("%4d", count), maxTolerance, rmsTolerance), e2);
1339             if (expectImprovement) {
1340                 // Better or equal. Equal applies if the asymptotic approximation was not used
1341                 // or computed the same result.
1342                 Assertions.assertTrue(e2.getRMS() <= e1.getRMS());
1343             } else {
1344                 // Worse
1345                 Assertions.assertTrue(e2.getRMS() > e1.getRMS());
1346             }
1347         }
1348     }
1349 
1350     /**
1351      * Test gamma Q with a value {@code a} so small that {@code tgamma(a) == inf}.
1352      * A direct port of the Boost code without changes will fail this test.
1353      */
1354     @ParameterizedTest
1355     @CsvSource({
1356         // This data needs verifying. Matlab, maxima and Boost all compute different values.
1357         // The values are the exponent of 2 to ensure tiny values are machine representable.
1358         // The following are from Boost using long double. This at least verifies that
1359         // the updated code computes close to the long double equivalent in the source
1360         // implementation.
1361         "-1074,-1074,3.67517082493672000135e-321",
1362         "-1074,-1073,3.67174622284245611609e-321",
1363         "-1074,-1072,3.66832162074819223109e-321",
1364         "-1074,-1000,3.4217502699611925034e-321",
1365         "-1074,-100,3.3960838512369590694e-322",
1366         "-1074,-10,3.13990203220802065461e-323",
1367         "-1074,-5,1.44243837956526236902e-323",
1368         "-1030,-1074,6.46542888972966038541e-308",
1369         "-1030,-1073,6.45940426601262169248e-308",
1370         "-1030,-1072,6.45337964229558300003e-308",
1371         "-1030,-1000,6.01960673466879712923e-308",
1372         "-1030,-100,5.97445389333973743599e-309",
1373         "-1030,-10,5.52377407118433787103e-310",
1374         "-1030,-5,2.53756443309180378013e-310",
1375     })
1376     void testGammaQTinyA(int ba, int bx, double q) {
1377         final double a = Math.scalb(1.0, ba);
1378         final double x = Math.scalb(1.0, bx);
1379         final double actualQ = BoostGamma.gammaQ(a, x);
1380 
1381         // Without changes to the Boost code that normalises by tgamma(a)
1382         // the result is zero. Check this does not occur.
1383         Assertions.assertNotEquals(0.0, actualQ);
1384 
1385         // Change tolerance for very small sub-normal result
1386         if (q < 1e-320) {
1387             // Within 1 ULP
1388             TestUtils.assertEquals(q, actualQ, 1);
1389         } else {
1390             // Sub-normal argument a.
1391             // The worst case here is 260 ULP. The relative error is OK.
1392             final double relError = 1e-13;
1393             Assertions.assertEquals(q, actualQ, q * relError);
1394         }
1395     }
1396 
1397     /**
1398      * Gamma function with Lanczos support.
1399      *
1400      * <p>This is the original Boost implementation here for reference. For {@code z}
1401      * in the range [-20, 20] the function is not as accurate as the NSWC
1402      * Library of Mathematics Subroutines.
1403      *
1404      * <p>This function and the tests that exercise it can be revisited if
1405      * improvements are made to the reference implementation.
1406      *
1407      * @param z Argument z
1408      * @return gamma value
1409      */
1410     static double tgammaOriginal(double z) {
1411         double result = 1;
1412 
1413         if (z <= 0) {
1414             if (Math.rint(z) == z) {
1415                 // Pole error
1416                 return Double.NaN;
1417             }
1418             if (z <= -20) {
1419                 result = BoostGamma.tgamma(-z) * BoostGamma.sinpx(z);
1420                 // Checks for overflow, sub-normal or underflow have been removed.
1421                 return -Math.PI / result;
1422             }
1423 
1424             // shift z to > 1:
1425             // Q. Is this comment old? The shift is to > 0.
1426             // Spot tests in the Boost resources test z -> 0 due to a bug report.
1427             while (z < 0) {
1428                 result /= z;
1429                 z += 1;
1430             }
1431         }
1432         //
1433         // z is > 0
1434         //
1435 
1436         // Updated condition from z < MAX_FACTORIAL
1437         if ((Math.rint(z) == z) && (z <= MAX_FACTORIAL + 1)) {
1438             // Gamma(n) = (n-1)!
1439             result *= FACTORIAL[(int) z - 1];
1440         } else if (z < ROOT_EPSILON) {
1441             result *= 1 / z - EULER;
1442         } else {
1443             result *= Lanczos.lanczosSum(z);
1444             final double zgh = z + Lanczos.G - 0.5;
1445             final double lzgh = Math.log(zgh);
1446             if (z * lzgh > LOG_MAX_VALUE) {
1447                 // we're going to overflow unless this is done with care:
1448                 if (lzgh * z / 2 > LOG_MAX_VALUE) {
1449                     return Double.POSITIVE_INFINITY;
1450                 }
1451                 final double hp = Math.pow(zgh, (z / 2) - 0.25);
1452                 result *= hp / Math.exp(zgh);
1453                 // Check for overflow has been removed:
1454                 // if (Double.MAX_VALUE / hp < result) ... overflow
1455                 result *= hp;
1456             } else {
1457                 result *= Math.pow(zgh, z - 0.5) / Math.exp(zgh);
1458             }
1459         }
1460         return result;
1461     }
1462 
1463     /**
1464      * Computes the value of \( \Gamma(x) \).
1465      *
1466      * <p>Based on the <em>NSWC Library of Mathematics Subroutines</em> double
1467      * precision implementation, {@code DGAMMA}.
1468      *
1469      * <p>This is a partial implementation of the Commons Numbers 1.0 implementation
1470      * for testing the {@link LanczosApproximation}.
1471      * The Lanczos support is valid for {@code x > 20}.
1472      *
1473      * @param x Argument ({@code x > 20})
1474      * @return \( \Gamma(x) \)
1475      */
1476     static double gammaOriginal(double x) {
1477         Assertions.assertTrue(x > LANCZOS_THRESHOLD, "Unsupported x: " + x);
1478 
1479         final double y = x + LanczosApproximation.g() + 0.5;
1480         // Constant 2.506... is sqrt(2 pi)
1481         return 2.506628274631000502 / x *
1482             Math.pow(y, x + 0.5) *
1483             Math.exp(-y) * LanczosApproximation.value(x);
1484     }
1485 
1486     /**
1487      * Compute incomplete gamma Q assuming large X without the asymptotic approximation.
1488      *
1489      * @param a Argument a
1490      * @param x Argument x
1491      * @return incomplete gamma Q
1492      */
1493     private static double igammaQLargeXNoAsym(double a, double x) {
1494         return gammaIncompleteImp(a, x, true, Policy.getDefault(), NO_ASYM_APPROX);
1495     }
1496 
1497     /**
1498      * Compute incomplete gamma Q assuming large X without the asymptotic approximation.
1499      *
1500      * @param a Argument a
1501      * @param x Argument x
1502      * @return incomplete gamma P
1503      */
1504     private static double igammaPLargeXNoAsym(double a, double x) {
1505         return gammaIncompleteImp(a, x, false, Policy.getDefault(), NO_ASYM_APPROX);
1506     }
1507 
1508     /**
1509      * Compute incomplete gamma Q assuming large X.
1510      * Uses the asymptotic approximation for large argument, see: https://dlmf.nist.gov/8.11#E2.
1511      *
1512      * @param a Argument a
1513      * @param x Argument x
1514      * @return incomplete gamma Q
1515      */
1516     private static double igammaQLargeXWithAsym(double a, double x) {
1517         // Force use of the asymptotic approximation
1518         return gammaIncompleteImp(a, x, true, Policy.getDefault(), ASYM_APPROX);
1519     }
1520 
1521     /**
1522      * Compute incomplete gamma P assuming large X.
1523      * Uses the asymptotic approximation for large argument, see: https://dlmf.nist.gov/8.11#E2.
1524      *
1525      * @param a Argument a
1526      * @param x Argument x
1527      * @return incomplete gamma P
1528      */
1529     private static double igammaPLargeXWithAsym(double a, double x) {
1530         // Force use of the asymptotic approximation
1531         return gammaIncompleteImp(a, x, false, Policy.getDefault(), ASYM_APPROX);
1532     }
1533 
1534     /**
1535      * Partial implementation of igamma for normalised and x > small threshold. This
1536      * is used to test the switch to the asymptotic approximation for large x. The
1537      * predicate to determine the switch is passed as an argument. Adapted from
1538      * {@code boost::math::detail::gamma_incomplete_imp}.
1539      *
1540      * @param a Argument a
1541      * @param x Argument x
1542      * @param invert true to compute the upper value Q (default is lower value P)
1543      * @param pol Function evaluation policy
1544      * @param useAsymApprox Predicate to determine when to use the asymptotic approximation
1545      * @return gamma value
1546      */
1547     private static double gammaIncompleteImp(double a, double x,
1548             boolean invert, Policy pol, DoubleDoubleBiPredicate useAsymApprox) {
1549         if (Double.isNaN(a) || Double.isNaN(x) || a <= 0 || x < 0) {
1550             return Double.NaN;
1551         }
1552 
1553         // Assumption for testing.
1554         // Do not support int or half-int a.
1555         // Do not support small x.
1556         // These evaluations methods have been removed.
1557         Assertions.assertFalse(isIntOrHalfInt(a, x), () -> "Invalid a : " + a);
1558         Assertions.assertFalse(x < 1.1, () -> "Invalid x : " + x);
1559 
1560         double result = 0;
1561         int evalMethod;
1562 
1563         // Configurable switch to the asymptotic approximation.
1564         // The branch is used in Boost when:
1565         // ((x > 1000) && ((a < x) || (Math.abs(a - 50) / x < 1)))
1566         if (useAsymApprox.test(a, x)) {
1567             // This case was added after Boost 1_68_0.
1568             // See: https://github.com/boostorg/math/issues/168
1569             // It is a source of error when a ~ z as the asymptotic approximation
1570             // sums terms t_n+1 = t_n * (a - n - 1) / z starting from t_0 = 1.
1571             // These terms are close to 1 when a ~ z and the sum has many terms
1572             // with reduced precision.
1573 
1574             // calculate Q via asymptotic approximation:
1575             invert = !invert;
1576             evalMethod = 7;
1577         } else {
1578             //
1579             // Begin by testing whether we're in the "bad" zone
1580             // where the result will be near 0.5 and the usual
1581             // series and continued fractions are slow to converge:
1582             //
1583             boolean useTemme = false;
1584             if (a > 20) {
1585                 final double sigma = Math.abs((x - a) / a);
1586                 if (a > 200) {
1587                     //
1588                     // This limit is chosen so that we use Temme's expansion
1589                     // only if the result would be larger than about 10^-6.
1590                     // Below that the regular series and continued fractions
1591                     // converge OK, and if we use Temme's method we get increasing
1592                     // errors from the dominant erfc term as it's (inexact) argument
1593                     // increases in magnitude.
1594                     //
1595                     if (20 / a > sigma * sigma) {
1596                         useTemme = true;
1597                     }
1598                 } else {
1599                     // Note in this zone we can't use Temme's expansion for
1600                     // types longer than an 80-bit real:
1601                     // it would require too many terms in the polynomials.
1602                     if (sigma < 0.4) {
1603                         useTemme = true;
1604                     }
1605                 }
1606             }
1607             if (useTemme) {
1608                 evalMethod = 5;
1609             } else {
1610                 //
1611                 // Regular case where the result will not be too close to 0.5.
1612                 //
1613                 // Changeover here occurs at P ~ Q ~ 0.5
1614                 // Note that series computation of P is about x2 faster than continued fraction
1615                 // calculation of Q, so try and use the CF only when really necessary,
1616                 // especially for small x.
1617                 //
1618                 if (x - (1 / (3 * x)) < a) {
1619                     evalMethod = 2;
1620                 } else {
1621                     evalMethod = 4;
1622                     invert = !invert;
1623                 }
1624             }
1625         }
1626 
1627         switch (evalMethod) {
1628         case 2:
1629             // Compute P:
1630             result = BoostGamma.regularisedGammaPrefix(a, x);
1631             if (result != 0) {
1632                 //
1633                 // If we're going to be inverting the result then we can
1634                 // reduce the number of series evaluations by quite
1635                 // a few iterations if we set an initial value for the
1636                 // series sum based on what we'll end up subtracting it from
1637                 // at the end.
1638                 // Have to be careful though that this optimization doesn't
1639                 // lead to spurious numeric overflow. Note that the
1640                 // scary/expensive overflow checks below are more often
1641                 // than not bypassed in practice for "sensible" input
1642                 // values:
1643                 //
1644 
1645                 double initValue = 0;
1646                 boolean optimisedInvert = false;
1647                 if (invert) {
1648                     // Assume normalised=true
1649                     initValue = 1;
1650                     initValue /= result;
1651                     initValue *= -a;
1652                     optimisedInvert = true;
1653                 }
1654                 result *= BoostGamma.lowerGammaSeries(a, x, initValue, pol) / a;
1655                 if (optimisedInvert) {
1656                     invert = false;
1657                     result = -result;
1658                 }
1659             }
1660             break;
1661         case 4:
1662             // Compute Q:
1663             result = BoostGamma.regularisedGammaPrefix(a, x);
1664             if (result != 0) {
1665                 result *= BoostGamma.upperGammaFraction(a, x, pol);
1666             }
1667             break;
1668         case 5:
1669             // Call 53-bit version
1670             result = BoostGamma.igammaTemmeLarge(a, x);
1671             if (x >= a) {
1672                 invert = !invert;
1673             }
1674             break;
1675         case 7:
1676             // x is large,
1677             // Compute Q:
1678             result = BoostGamma.regularisedGammaPrefix(a, x);
1679             result /= x;
1680             if (result != 0) {
1681                 result *= BoostGamma.incompleteTgammaLargeX(a, x, pol);
1682             }
1683             break;
1684         default:
1685             Assertions.fail(String.format("Unknown evaluation method: %s %s", a, x));
1686         }
1687 
1688         if (result > 1) {
1689             result = 1;
1690         }
1691         if (invert) {
1692             result = 1 - result;
1693         }
1694 
1695         return result;
1696     }
1697 
1698     /**
1699      * Test if a is small and an integer or half-integer.
1700      *
1701      * @param a Argument a
1702      * @param x Argument x
1703      * @return true if an integer or half integer
1704      */
1705     private static boolean isIntOrHalfInt(double a, double x) {
1706         boolean isInt;
1707         boolean isHalfInt;
1708         final boolean isSmallA = (a < 30) && (a <= x + 1) && (-x < LOG_MIN_VALUE);
1709         if (isSmallA) {
1710             final double fa = Math.floor(a);
1711             isInt = fa == a;
1712             isHalfInt = !isInt && (Math.abs(fa - a) == 0.5f);
1713         } else {
1714             isInt = isHalfInt = false;
1715         }
1716         return isInt || isHalfInt;
1717     }
1718 
1719     /**
1720      * Incomplete tgamma for large X.
1721      *
1722      * <p>Uses the default epsilon and iterations.
1723      *
1724      * @param a Argument a
1725      * @param x Argument x
1726      * @return incomplete tgamma
1727      */
1728     private static double incompleteTgammaLargeX(double a, double x) {
1729         return BoostGamma.incompleteTgammaLargeX(a, x, Policy.getDefault());
1730     }
1731 
1732     /**
1733      * Natural logarithm of the derivative of the regularised lower incomplete gamma.
1734      *
1735      * <pre>
1736      * a * log(x) - log(x) - x - lgamma(a)
1737      * </pre>
1738      *
1739      * <p>Always computes using logs.
1740      *
1741      * @param a Argument a
1742      * @param x Argument x
1743      * @return log p derivative
1744      */
1745     private static double logGammaPDerivative1(double a, double x) {
1746         if (a == 1) {
1747             // Special case
1748             return -x;
1749         }
1750         // No argument checks
1751         return a * Math.log(x) - x - BoostGamma.lgamma(a) - Math.log(x);
1752 
1753         //// Note:
1754         //// High precision. Only marginally lowers the error.
1755         //return new BigDecimal(a).subtract(BigDecimal.ONE)
1756         //    .multiply(new BigDecimal(Math.log(x)))
1757         //    .subtract(new BigDecimal(x))
1758         //    .subtract(new BigDecimal(BoostGamma.lgamma(a))).doubleValue();
1759     }
1760 
1761     /**
1762      * Natural logarithm of the derivative of the regularised lower incomplete gamma.
1763      *
1764      * <pre>
1765      * a * log(x) - log(x) - x - lgamma(a)
1766      * </pre>
1767      *
1768      * <p>Computed using the logarithm of the gamma p derivative if this is finite; else
1769      * uses logs.
1770      *
1771      * @param a Argument a
1772      * @param x Argument x
1773      * @return log p derivative
1774      */
1775     private static double logGammaPDerivative2(double a, double x) {
1776         //
1777         // Usual error checks first:
1778         //
1779         if (Double.isNaN(a) || Double.isNaN(x) || a <= 0 || x < 0) {
1780             return Double.NaN;
1781         }
1782         //
1783         // Now special cases:
1784         //
1785         if (x == 0) {
1786             return (a > 1) ? Double.NEGATIVE_INFINITY : (a == 1) ? 0 : Double.POSITIVE_INFINITY;
1787         }
1788         if (a == 1) {
1789             return -x;
1790         }
1791         //
1792         // Normal case:
1793         //
1794 
1795         // Note: This may be computing log(exp(result)) for a round-trip of 'result'.
1796         // Variations were created that:
1797         // 1. Called BoostGamma.regularisedGammaPrefix(a, x) and processed the result.
1798         //    This avoids a log(exp(result)) round-trip.
1799         // 2. Repeated the logic of BoostGamma.regularisedGammaPrefix(a, x) and directly
1800         //    returned the log result.
1801         // The variations may be faster but are no more accurate on the current test data.
1802 
1803         final double v = BoostGamma.gammaPDerivative(a, x);
1804         return Double.isFinite(v) && v != 0 ?
1805             Math.log(v) :
1806             logGammaPDerivative1(a, x);
1807     }
1808 
1809     @ParameterizedTest
1810     @CsvSource({
1811         "NaN, 1, NaN",
1812         "1, NaN, NaN",
1813         // Allow calling with infinity
1814         "Infinity, 0, 1",
1815         "Infinity, 1, 0",
1816         "Infinity, -1, Infinity",
1817         // z <= 0 || z+delta <= 0
1818         // Pole errors
1819         "-1, 0.5, NaN",
1820         "-1.5, 0.5, NaN",
1821         "0.5, -1.5, NaN",
1822     })
1823     void testGammaDeltaRatioEdgeCases(double a, double delta, double expected) {
1824         Assertions.assertEquals(expected, BoostGamma.tgammaDeltaRatio(a, delta));
1825     }
1826 
1827     @ParameterizedTest
1828     @CsvSource({
1829         "0, 1, NaN",
1830         "-1, 1, NaN",
1831         "Infinity, 1, NaN",
1832         "NaN, 1, NaN",
1833         "1, 0, NaN",
1834         "1, -1, NaN",
1835         "1, Infinity, NaN",
1836         "1, NaN, NaN",
1837         // underflow
1838         "0.5, 500, 0",
1839         // overflow
1840         "500, 0.5, Infinity",
1841     })
1842     void testGammaRatioEdgeCases(double a, double b, double expected) {
1843         Assertions.assertEquals(expected, BoostGamma.tgammaRatio(a, b));
1844     }
1845 
1846     /**
1847      * tgamma ratio spot tests extracted from
1848      * {@code boost/libs/math/test/test_tgamma_ratio.hpp}.
1849      */
1850     @Test
1851     void testGammaRatioSpotTests() {
1852         final int tol = 20;
1853         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -500), 180.25, 8.0113754557649679470816892372669519037339812035512e-178, 3 * tol);
1854         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -525), 192.25, 1.5966560279353205461166489184101261541784867035063e-197, 3 * tol);
1855         assertClose(BoostGamma::tgammaRatio, 182.25, Math.scalb(1.0, -500), 4.077990437521002194346763299159975185747917450788e+181, 3 * tol);
1856         assertClose(BoostGamma::tgammaRatio, 193.25, Math.scalb(1.0, -525), 1.2040790040958522422697601672703926839178050326148e+199, 3 * tol);
1857         assertClose(BoostGamma::tgammaRatio, 193.25, 194.75, 0.00037151765099653237632823607820104961270831942138159, 3 * tol);
1858         //
1859         // Some bug cases from Rocco Romeo:
1860         //
1861         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 100, 1.20390418056093374068585549133304106854441830616070800417660e151, tol);
1862         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 150, 2.94980580122226729924781231239336413648584663386992050529324e46, tol);
1863         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 180, 1.00669209319561468911303652019446665496398881230516805140750e-20, tol);
1864         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 220, 1.08230263539550701700187215488533416834407799907721731317227e-112, tol);
1865         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 260, 7.62689807594728483940172477902929825624752380292252137809206e-208, tol);
1866         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1020), 290, 5.40206998243175672775582485422795773284966068149812072521290e-281, tol);
1867         assertClose(BoostGamma::tgammaDeltaRatio, Math.scalb(1.0, -1020), Math.scalb(1.0, -1020), 2, tol);
1868         // This is denorm_min at double precision:
1869         assertClose(BoostGamma::tgammaRatio, Math.scalb(1.0, -1074), 200, 5.13282785052571536804189023927976812551830809667482691717029e-50, tol * 50);
1870         assertClose(BoostGamma::tgammaRatio, 200, Math.scalb(1.0, -1074), 1.94824379293682687942882944294875087145333536754699303593931e49, tol * 10);
1871         assertClose(BoostGamma::tgammaDeltaRatio, Math.scalb(1.0, -1074), 200, 5.13282785052571536804189023927976812551830809667482691717029e-50, tol * 10);
1872         assertClose(BoostGamma::tgammaDeltaRatio, 200, Math.scalb(1.0, -1074), 1, tol);
1873 
1874         // Test simple negative handling
1875         for (final double z : new double[] {-0.5, -15.5, -25.5}) {
1876             for (final double delta : new double[] {0.25, -0.25}) {
1877                 Assertions.assertEquals(BoostGamma.tgamma(z) / BoostGamma.tgamma(z + delta), BoostGamma.tgammaDeltaRatio(z, delta));
1878             }
1879         }
1880     }
1881 
1882     @ParameterizedTest
1883     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"TGAMMA_DELTA_RATIO.*"})
1884     void testGammaDeltaRatio(BiTestCase tc) {
1885         assertFunction(tc);
1886     }
1887 
1888     @ParameterizedTest
1889     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"TGAMMA_RATIO.*"})
1890     void testGammaRatio(BiTestCase tc) {
1891         assertFunction(tc);
1892     }
1893 
1894     /**
1895      * Helper function for ratio of gamma functions to negate the delta argument.
1896      *
1897      * <p>\[ tgamma_ratio(z, delta) = \frac{\Gamma(z)}{\Gamma(z - delta)} \]
1898      *
1899      * @param z Argument z
1900      * @param delta The difference
1901      * @return gamma ratio
1902      */
1903     private static double tgammaDeltaRatioNegative(double z, double delta) {
1904         return BoostGamma.tgammaDeltaRatio(z, -delta);
1905     }
1906 
1907     /**
1908      * Assert the function is close to the expected value.
1909      *
1910      * @param fun Function
1911      * @param x Input value
1912      * @param expected Expected value
1913      * @param tolerance the tolerance
1914      */
1915     private static void assertClose(DoubleUnaryOperator fun, double x, double expected, int tolerance) {
1916         final double actual = fun.applyAsDouble(x);
1917         TestUtils.assertEquals(expected, actual, tolerance, null, () -> Double.toString(x));
1918     }
1919 
1920     /**
1921      * Assert the function is close to the expected value.
1922      *
1923      * @param fun Function
1924      * @param x Input value
1925      * @param y Input value
1926      * @param expected Expected value
1927      * @param tolerance the tolerance
1928      */
1929     private static void assertClose(DoubleBinaryOperator fun, double x, double y, double expected, int tolerance) {
1930         final double actual = fun.applyAsDouble(x, y);
1931         TestUtils.assertEquals(expected, actual, tolerance, null, () -> x + ", " + y);
1932     }
1933 
1934     /**
1935      * Assert the function is equal to the expected value.
1936      *
1937      * @param fun Function
1938      * @param x Input value
1939      * @param y Input value
1940      * @param expected Expected value
1941      */
1942     private static void assertEquals(DoubleBinaryOperator fun, double x, double y, double expected) {
1943         final double actual = fun.applyAsDouble(x, y);
1944         Assertions.assertEquals(expected, actual, () -> x + ", " + y);
1945     }
1946 
1947     /**
1948      * Assert the function using extended precision.
1949      *
1950      * @param tc Test case
1951      */
1952     private static void assertFunction(TestCase tc) {
1953         final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1954         try (DataReader in = new DataReader(tc.getFilename())) {
1955             while (in.next()) {
1956                 try {
1957                     final double x = in.getDouble(0);
1958                     final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1959                     final double actual = tc.getFunction().applyAsDouble(x);
1960                     TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1961                         () -> tc + " x=" + x);
1962                 } catch (final NumberFormatException ex) {
1963                     Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1964                 }
1965             }
1966         } catch (final IOException ex) {
1967             Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1968         }
1969 
1970         assertRms(tc, stats);
1971     }
1972 
1973     /**
1974      * Assert the function using extended precision.
1975      *
1976      * @param tc Test case
1977      */
1978     private static void assertFunction(BiTestCase tc) {
1979         final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1980         try (DataReader in = new DataReader(tc.getFilename())) {
1981             while (in.next()) {
1982                 try {
1983                     final double x = in.getDouble(0);
1984                     final double y = in.getDouble(1);
1985                     final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1986                     final double actual = tc.getFunction().applyAsDouble(x, y);
1987                     TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1988                         () -> tc + " x=" + x + ", y=" + y);
1989                 } catch (final NumberFormatException ex) {
1990                     Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1991                 }
1992             }
1993         } catch (final IOException ex) {
1994             Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1995         }
1996 
1997         assertRms(tc, stats);
1998     }
1999 
2000     /**
2001      * Assert the Root Mean Square (RMS) error of the function is below the allowed
2002      * maximum for the specified TestError.
2003      *
2004      * @param te Test error
2005      * @param stats Error statistics
2006      */
2007     private static void assertRms(TestError te, TestUtils.ErrorStatistics stats) {
2008         final double rms = stats.getRMS();
2009         //debugRms(te.toString(), stats.getMaxAbs(), rms, stats.getMean(), stats.size());
2010         Assertions.assertTrue(rms <= te.getRmsTolerance(),
2011             () -> String.format("%s RMS %s < %s", te, rms, te.getRmsTolerance()));
2012     }
2013 
2014     /**
2015      * Output the maximum and RMS ulp for the named test. Used for reporting the
2016      * errors and setting appropriate test tolerances. This is relevant across
2017      * different JDK implementations where the java.util.Math functions used in
2018      * BoostGamma may compute to different accuracy.
2019      *
2020      * @param name Test name
2021      * @param maxAbsUlp Maximum |ulp|
2022      * @param rmsUlp RMS ulp
2023      * @param meanUlp Mean ulp
2024      * @param size Number of measurements
2025      */
2026     private static void debugRms(String name, double maxAbsUlp, double rmsUlp, double meanUlp, int size) {
2027         // CHECKSTYLE: stop regexp
2028         System.out.printf("%-35s   max %10.6g   RMS %10.6g   mean %14.6g  n %4d%n",
2029             name, maxAbsUlp, rmsUlp, meanUlp, size);
2030     }
2031 }