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 org.junit.jupiter.api.Assertions;
24  import org.junit.jupiter.api.MethodOrderer;
25  import org.junit.jupiter.api.Test;
26  import org.junit.jupiter.api.TestMethodOrder;
27  import org.junit.jupiter.params.ParameterizedTest;
28  import org.junit.jupiter.params.provider.CsvSource;
29  import org.junit.jupiter.params.provider.EnumSource;
30  import org.junit.jupiter.params.provider.EnumSource.Mode;
31  
32  /**
33   * Tests for {@link BoostBeta}.
34   *
35   * <p>Note: Some resource data files used in these tests have been extracted
36   * from the Boost test files for the beta functions.
37   */
38  @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
39  class BoostBetaTest {
40      /**
41       * Represents an operation upon three {@code double}-valued operands and producing a
42       * {@code double}-valued result.
43       */
44      interface DoubleTernaryOperator {
45          /**
46           * Applies this operator to the given operands.
47           *
48           * @param x the first operand
49           * @param y the second operand
50           * @param z the third operand
51           * @return the operator result
52           */
53          double applyAsDouble(double x, double y, double z);
54      }
55  
56      /** Define the expected error for a test. */
57      private interface TestError {
58          /**
59           * @return maximum allowed error
60           */
61          double getTolerance();
62  
63          /**
64           * @return maximum allowed RMS error
65           */
66          double getRmsTolerance();
67      }
68  
69      /**
70       * Define the test cases for each resource file for two argument functions.
71       * This encapsulates the function to test, the expected maximum and RMS error, and
72       * the resource file containing the data.
73       *
74       * <h2>Note on accuracy</h2>
75       *
76       * <p>The Boost functions use the default policy of internal promotion
77       * of double to long double if it offers more precision. Java does not
78       * support long double computation. Tolerances have been set to allow tests to
79       * pass. Spot checks on larger errors have been verified against the reference
80       * implementation compiled with promotion of double <strong>disabled</strong>.
81       *
82       * @see <a href="https://www.boost.org/doc/libs/1_77_0/libs/math/doc/html/math_toolkit/relative_error.html">Relative error</a>
83       * @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>
84       */
85      private enum BiTestCase implements TestError {
86          // beta(a, b)
87          // Note that the worst errors occur when a or b are large, and that
88          // when this is the case the result is very close to zero, so absolute
89          // errors will be very small.
90          /** beta Boost small data. */
91          BETA_SMALL(BoostBeta::beta, "beta_small_data.csv", 4, 1.7),
92          /** beta Boost medium data. */
93          BETA_MED(BoostBeta::beta, "beta_med_data.csv", 200, 35),
94          /** beta Boost divergent data. */
95          BETA_EXP(BoostBeta::beta, "beta_exp_data.csv", 17, 3.6),
96          // LogBeta based implementation is worse
97          /** beta Boost small data. */
98          BETA1_SMALL(BoostBetaTest::beta, "beta_small_data.csv", 110, 28),
99          /** beta Boost medium data. */
100         BETA1_MED(BoostBetaTest::beta, "beta_med_data.csv", 280, 46),
101         /** beta Boost divergent data. */
102         BETA1_EXP(BoostBetaTest::beta, "beta_exp_data.csv", 28, 4.5),
103         /** binomial coefficient Boost small argument data. */
104         BINOMIAL_SMALL(BoostBetaTest::binomialCoefficient, "binomial_small_data.csv", -2, 0.5),
105         /** binomial coefficient Boost large argument data. */
106         BINOMIAL_LARGE(BoostBetaTest::binomialCoefficient, "binomial_large_data.csv", 5, 1.1),
107         /** binomial coefficient extra large argument data. */
108         BINOMIAL_XLARGE(BoostBetaTest::binomialCoefficient, "binomial_extra_large_data.csv", 9, 2),
109         /** binomial coefficient huge argument data. */
110         BINOMIAL_HUGE(BoostBetaTest::binomialCoefficient, "binomial_huge_data.csv", 9, 2),
111         // Using the beta function is worse
112         /** binomial coefficient Boost large argument data computed using the beta function. */
113         BINOMIAL1_LARGE(BoostBetaTest::binomialCoefficient1, "binomial_large_data.csv", 31, 9),
114         /** binomial coefficient huge argument data computed using the beta function. */
115         BINOMIAL1_HUGE(BoostBetaTest::binomialCoefficient1, "binomial_huge_data.csv", 70, 19);
116 
117         /** The function. */
118         private final DoubleBinaryOperator fun;
119 
120         /** The filename containing the test data. */
121         private final String filename;
122 
123         /** The field containing the expected value. */
124         private final int expected;
125 
126         /** The maximum allowed ulp. */
127         private final double maxUlp;
128 
129         /** The maximum allowed RMS ulp. */
130         private final double rmsUlp;
131 
132         /**
133          * Instantiates a new test case.
134          *
135          * @param fun function to test
136          * @param filename Filename of the test data
137          * @param maxUlp maximum allowed ulp
138          * @param rmsUlp maximum allowed RMS ulp
139          */
140         BiTestCase(DoubleBinaryOperator fun, String filename, double maxUlp, double rmsUlp) {
141             this(fun, filename, 2, maxUlp, rmsUlp);
142         }
143 
144         /**
145          * Instantiates a new test case.
146          *
147          * @param fun function to test
148          * @param filename Filename of the test data
149          * @param expected Expected result field index
150          * @param maxUlp maximum allowed ulp
151          * @param rmsUlp maximum allowed RMS ulp
152          */
153         BiTestCase(DoubleBinaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
154             this.fun = fun;
155             this.filename = filename;
156             this.expected = expected;
157             this.maxUlp = maxUlp;
158             this.rmsUlp = rmsUlp;
159         }
160 
161         /**
162          * @return function to test
163          */
164         DoubleBinaryOperator getFunction() {
165             return fun;
166         }
167 
168         /**
169          * @return Filename of the test data
170          */
171         String getFilename() {
172             return filename;
173         }
174 
175         /**
176          * @return Expected result field index
177          */
178         int getExpectedField() {
179             return expected;
180         }
181 
182         @Override
183         public double getTolerance() {
184             return maxUlp;
185         }
186 
187         @Override
188         public double getRmsTolerance() {
189             return rmsUlp;
190         }
191     }
192 
193     /**
194      * Define the test cases for each resource file for three argument functions.
195      * This encapsulates the function to test, the expected maximum and RMS error, and
196      * the resource file containing the data.
197      */
198     private enum TriTestCase implements TestError {
199         /** ibeta derivative Boost small integer data. */
200         IBETA_DERIV_SMALL_INT(BoostBeta::ibetaDerivative, "ibeta_derivative_small_int_data.csv", 60, 13),
201         /** ibeta derivative Boost small data. */
202         IBETA_DERIV_SMALL(BoostBeta::ibetaDerivative, "ibeta_derivative_small_data.csv", 22, 4),
203         /** ibeta derivative Boost medium data. */
204         IBETA_DERIV_MED(BoostBeta::ibetaDerivative, "ibeta_derivative_med_data.csv", 150, 33),
205         /** ibeta derivative Boost large and diverse data. */
206         IBETA_DERIV_LARGE(BoostBeta::ibetaDerivative, "ibeta_derivative_large_data.csv", 3900, 260),
207         // LogGamma based implementation is worse
208         /** ibeta derivative Boost small integer data. */
209         IBETA_DERIV1_SMALL_INT(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_small_int_data.csv", 220, 55),
210         /** ibeta derivative Boost small data. */
211         IBETA_DERIV1_SMALL(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_small_data.csv", 75, 10.5),
212         /** ibeta derivative Boost medium data. */
213         IBETA_DERIV1_MED(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_med_data.csv", 1500, 300),
214         /** ibeta derivative Boost large and diverse data. */
215         IBETA_DERIV1_LARGE(BoostBetaTest::ibetaDerivative1, "ibeta_derivative_large_data.csv", 9e7, 250000),
216         // LogBeta based implementation is worse
217         /** ibeta derivative Boost small integer data. */
218         IBETA_DERIV2_SMALL_INT(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_small_int_data.csv", 180, 31),
219         /** ibeta derivative Boost small data. */
220         IBETA_DERIV2_SMALL(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_small_data.csv", 75, 8.5),
221         /** ibeta derivative Boost medium data. */
222         IBETA_DERIV2_MED(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_med_data.csv", 500, 85),
223         /** ibeta derivative Boost large and diverse data. */
224         IBETA_DERIV2_LARGE(BoostBetaTest::ibetaDerivative2, "ibeta_derivative_large_data.csv", 28000, 1200),
225 
226         /** ibeta Boost small integer data. */
227         IBETA_SMALL_INT(BoostBeta::beta, "ibeta_small_int_data.csv", 48, 11),
228         /** ibeta Boost small data. */
229         IBETA_SMALL(BoostBeta::beta, "ibeta_small_data.csv", 17, 3.3),
230         /** ibeta Boost medium data. */
231         IBETA_MED(BoostBeta::beta, "ibeta_med_data.csv", 190, 20),
232         /** ibeta Boost large and diverse data. */
233         IBETA_LARGE(BoostBeta::beta, "ibeta_large_data.csv", 1300, 50),
234         /** ibetac Boost small integer data. */
235         IBETAC_SMALL_INT(BoostBeta::betac, "ibeta_small_int_data.csv", 4, 57, 11),
236         /** ibetac Boost small data. */
237         IBETAC_SMALL(BoostBeta::betac, "ibeta_small_data.csv", 4, 14, 3.2),
238         /** ibetac Boost medium data. */
239         IBETAC_MED(BoostBeta::betac, "ibeta_med_data.csv", 4, 130, 24),
240         /** ibetac Boost large and diverse data. */
241         IBETAC_LARGE(BoostBeta::betac, "ibeta_large_data.csv", 4, 7000, 220),
242         /** regularised ibeta Boost small integer data. */
243         RBETA_SMALL_INT(BoostBeta::ibeta, "ibeta_small_int_data.csv", 5, 7.5, 1.2),
244         /** regularised ibeta Boost small data. */
245         RBETA_SMALL(BoostBeta::ibeta, "ibeta_small_data.csv", 5, 14, 3.3),
246         /** regularised ibeta Boost medium data. */
247         RBETA_MED(BoostBeta::ibeta, "ibeta_med_data.csv", 5, 200, 28),
248         /** regularised ibeta Boost large and diverse data. */
249         RBETA_LARGE(BoostBeta::ibeta, "ibeta_large_data.csv", 5, 2400, 100),
250         /** regularised ibetac Boost small integer data. */
251         RBETAC_SMALL_INT(BoostBeta::ibetac, "ibeta_small_int_data.csv", 6, 8, 1.6),
252         /** regularised ibetac Boost small data. */
253         RBETAC_SMALL(BoostBeta::ibetac, "ibeta_small_data.csv", 6, 11, 2.75),
254         /** regularised ibetac Boost medium data. */
255         RBETAC_MED(BoostBeta::ibetac, "ibeta_med_data.csv", 6, 105, 23),
256         /** regularised ibetac Boost large and diverse data. */
257         RBETAC_LARGE(BoostBeta::ibetac, "ibeta_large_data.csv", 6, 4000, 180),
258 
259         // Classic continued fraction representation is:
260         // - worse on small data
261         // - comparable (or better) on medium data
262         // - much worse on large data
263         /** regularised ibeta Boost small data using the classic continued fraction evaluation. */
264         RBETA1_SMALL(BoostBetaTest::ibeta, "ibeta_small_data.csv", 5, 45, 5),
265         /** regularised ibeta Boost small data using the classic continued fraction evaluation. */
266         RBETA1_MED(BoostBetaTest::ibeta, "ibeta_med_data.csv", 5, 200, 26),
267         /** regularised ibeta Boost large and diverse data. */
268         RBETA1_LARGE(BoostBetaTest::ibeta, "ibeta_large_data.csv", 5, 150000, 7500),
269         /** regularised ibeta Boost small data using the classic continued fraction evaluation. */
270         RBETAC1_SMALL(BoostBetaTest::ibetac, "ibeta_small_data.csv", 6, 30, 4.5),
271         /** regularised ibeta Boost small data using the classic continued fraction evaluation. */
272         RBETAC1_MED(BoostBetaTest::ibetac, "ibeta_med_data.csv", 6, 100, 22),
273         /** regularised ibetac Boost large and diverse data. */
274         RBETAC1_LARGE(BoostBetaTest::ibetac, "ibeta_large_data.csv", 6, 370000, 11000);
275 
276         /** The function. */
277         private final DoubleTernaryOperator fun;
278 
279         /** The filename containing the test data. */
280         private final String filename;
281 
282         /** The field containing the expected value. */
283         private final int expected;
284 
285         /** The maximum allowed ulp. */
286         private final double maxUlp;
287 
288         /** The maximum allowed RMS ulp. */
289         private final double rmsUlp;
290 
291         /**
292          * Instantiates a new test case.
293          *
294          * @param fun function to test
295          * @param filename Filename of the test data
296          * @param maxUlp maximum allowed ulp
297          * @param rmsUlp maximum allowed RMS ulp
298          */
299         TriTestCase(DoubleTernaryOperator fun, String filename, double maxUlp, double rmsUlp) {
300             this(fun, filename, 3, maxUlp, rmsUlp);
301         }
302 
303         /**
304          * Instantiates a new test case.
305          *
306          * @param fun function to test
307          * @param filename Filename of the test data
308          * @param expected Expected result field index
309          * @param maxUlp maximum allowed ulp
310          * @param rmsUlp maximum allowed RMS ulp
311          */
312         TriTestCase(DoubleTernaryOperator fun, String filename, int expected, double maxUlp, double rmsUlp) {
313             this.fun = fun;
314             this.filename = filename;
315             this.expected = expected;
316             this.maxUlp = maxUlp;
317             this.rmsUlp = rmsUlp;
318         }
319 
320         /**
321          * @return function to test
322          */
323         DoubleTernaryOperator getFunction() {
324             return fun;
325         }
326 
327         /**
328          * @return Filename of the test data
329          */
330         String getFilename() {
331             return filename;
332         }
333 
334         /**
335          * @return Expected result field index
336          */
337         int getExpectedField() {
338             return expected;
339         }
340 
341         @Override
342         public double getTolerance() {
343             return maxUlp;
344         }
345 
346         @Override
347         public double getRmsTolerance() {
348             return rmsUlp;
349         }
350     }
351 
352     @ParameterizedTest
353     @CsvSource({
354         // Argument a > 0
355         "NaN, 1, NaN",
356         "0, 1, NaN",
357         "-1, 1, NaN",
358         // Argument b > 0
359         "1, NaN, NaN",
360         "1, 0, NaN",
361         "1, -1, NaN",
362     })
363     void testBetaEdgeCases(double a, double b, double expected) {
364         Assertions.assertEquals(expected, BoostBeta.beta(a, b), "beta");
365     }
366 
367     /**
368      * beta spot tests extracted from
369      * {@code boost/libs/math/test/test_beta.hpp}.
370      */
371     @Test
372     void testBetaSpotTests() {
373         final int tolerance = 20;
374         // small = epsilon / 1024
375         final double small = Math.ulp(1.0) / 1024;
376         assertClose(BoostBeta::beta, 1, 1, 1, 0);
377         assertClose(BoostBeta::beta, 1, 4, 0.25, 0);
378         assertClose(BoostBeta::beta, 4, 1, 0.25, 0);
379         assertClose(BoostBeta::beta, small, 4, 1 / small, 0);
380         assertClose(BoostBeta::beta, 4, small, 1 / small, 0);
381         assertClose(BoostBeta::beta, 4, 20, 0.00002823263692828910220214568040654997176736, tolerance);
382         assertClose(BoostBeta::beta, 0.0125, 0.000023, 43558.24045647538375006349016083320744662, tolerance * 2);
383 
384         // Additional tests
385 
386         // a + b > 1e10
387         assertClose(BoostBeta::beta, 1e6, 1e6, 0, 0);
388         assertClose(BoostBeta::beta, 1e10, 100, 0, 0);
389         assertClose(BoostBeta::beta, 1e11, 1, 1e-11, 0);
390         assertClose(BoostBeta::beta, 1e11, 2, 1 / (1e11 * (1e11 + 1)), 5);
391 
392         // a + b == a; b > epsilon
393         assertClose(BoostBeta::beta, 5, 0x1.0p-51, 2.2517998136852459167e15, 5);
394         // a + b == b; a > epsilon
395         assertClose(BoostBeta::beta, 0x1.0p-50, 11, 1.125899906842621071e15, 5);
396     }
397 
398     @ParameterizedTest
399     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"BETA_.*", "BETA1_.*"})
400     void testBeta(BiTestCase tc) {
401         assertFunction(tc);
402     }
403 
404     @ParameterizedTest
405     @CsvSource({
406         // k=0 or k=n
407         "1, 0, 1",
408         "42, 0, 1",
409         "1, 1, 1",
410         "42, 42, 1",
411         // k=1 or k=n-1
412         "6, 1, 6",
413         "42, 1, 42",
414         "6, 5, 6",
415         "42, 41, 42",
416     })
417     void testBinomialCoefficientEdgeCases(int n, int k, double expected) {
418         Assertions.assertEquals(expected, BoostBeta.binomialCoefficient(n, k));
419     }
420 
421     /**
422      * Binomial coefficient spot tests extracted from
423      * {@code boost/libs/math/test/test_binomial_coeff.cpp}.
424      */
425     @Test
426     void testBinomialCoefficientSpotTests() {
427         Assertions.assertEquals(1, BoostBeta.binomialCoefficient(20, 0));
428         Assertions.assertEquals(20, BoostBeta.binomialCoefficient(20, 1));
429         Assertions.assertEquals(190, BoostBeta.binomialCoefficient(20, 2));
430         Assertions.assertEquals(1140, BoostBeta.binomialCoefficient(20, 3));
431         Assertions.assertEquals(1, BoostBeta.binomialCoefficient(20, 20));
432         Assertions.assertEquals(20, BoostBeta.binomialCoefficient(20, 19));
433         Assertions.assertEquals(190, BoostBeta.binomialCoefficient(20, 18));
434         Assertions.assertEquals(1140, BoostBeta.binomialCoefficient(20, 17));
435         Assertions.assertEquals(184756, BoostBeta.binomialCoefficient(20, 10));
436 
437         // Requires tolerance 50 if using the beta function approximation
438         // in <boost/math/special_functions/binomial.hpp>.
439         // Lower if using BinomialCoefficientDouble (see below).
440         int tolerance = 2;
441 
442         // 0 ulp
443         assertClose(BoostBetaTest::binomialCoefficient, 100, 5, 7.528752e7, tolerance);
444         // 1 ulp
445         assertClose(BoostBetaTest::binomialCoefficient, 100, 81, 1.323415729392122674e20, tolerance);
446 
447         // 0 ulp
448         assertClose(BoostBetaTest::binomialCoefficient, 300, 3, 4.45510e6, tolerance);
449         // 0 ulp
450         assertClose(BoostBetaTest::binomialCoefficient, 300, 7, 4.04385595614e13, tolerance);
451         // -3 ulp / 1 ulp
452         assertClose(BoostBetaTest::binomialCoefficient, 300, 290, 1.39832023324170177e18, tolerance);
453         // 36 ulp / 2 ulp
454         assertClose(BoostBetaTest::binomialCoefficient, 300, 275, 1.953265141442868389822364184842211512e36, tolerance);
455 
456         // Additional tests
457 
458         // Overflow if computed using
459         // result = 1
460         // for i in [1, m]:
461         //   result *= n - m + i
462         //   result /= i
463         // The final term must use divide by i first.
464         tolerance = 5;
465         assertClose(BoostBetaTest::binomialCoefficient, 1786388282, 38, 7.187239013254065384599502085053593e306, tolerance);
466         assertClose(BoostBetaTest::binomialCoefficient, 1914878305, 38, 100.6570419073661447979173868523364e306, tolerance);
467         assertClose(BoostBetaTest::binomialCoefficient, 1179067476, 39, 30.22890249420109200962786203300876e306, tolerance);
468         // n == 2^31 - 1
469         assertClose(BoostBetaTest::binomialCoefficient, 2147483647, 37, 1.388890512412231479281222156415993e302, tolerance);
470     }
471 
472     @ParameterizedTest
473     @EnumSource(value = BiTestCase.class, mode = Mode.MATCH_ANY, names = {"BINOM.*"})
474     void testBinomialCoefficient(BiTestCase tc) {
475         assertFunction(tc);
476     }
477 
478     /**
479      * Demonstrate the binomial coefficient is better without the beta function at large k.
480      */
481     @ParameterizedTest
482     @CsvSource({
483         "500, 225, 9.5909622148251792594e147",
484         "500, 250, 1.1674431578827768292e149",
485         "600, 300, 1.3510794199619426851e179",
486         "700, 350, 1.5857433585316795488e209",
487         "800, 400, 1.8804244186835312701e239",
488         "1000, 500, 2.7028824094543656952e299",
489         "100000, 50, 3.2479111644852887358e185",
490         "100000, 70, 8.1490000781382598363e249",
491         "100000, 80, 1.3537701492763430639e281",
492         "100000, 85, 3.4252195975122556458e296",
493         "1030, 515, 2.8596413729978081638e308",
494     })
495     void testBinomialCoefficientLargeK(int n, int k, double nCk) {
496         final double bc1 = BoostBeta.binomialCoefficient(n, k);
497         final double bc2 = BoostBetaTest.binomialCoefficient1(n, k);
498         assertCloser("nCk", nCk, bc1, bc2);
499     }
500 
501     @ParameterizedTest
502     @CsvSource({
503         // No protection against overflow when k ~ n/2, only for k << n
504         "1040, 450, 2.3101613255412135615e307",
505         "1029, 514, 1.4298206864989040819e308",
506     })
507     void testBinomialCoefficientOverflowAtLargeK(int n, int k, double nCk) {
508         // Note: This could be made to work correctly but is not required for the
509         // current use cases.
510         Assertions.assertEquals(Double.POSITIVE_INFINITY, BoostBeta.binomialCoefficient(n, k));
511 
512         // Using the beta function is not very accurate but it does work
513         Assertions.assertEquals(nCk, BoostBetaTest.binomialCoefficient1(n, k), nCk * 1e-12);
514     }
515 
516     @ParameterizedTest
517     @CsvSource({
518         // Argument a > 0
519         "NaN, 1, 0.5, NaN",
520         "0, 1, 0.5, NaN",
521         "-1, 1, 0.5, NaN",
522         // Argument b > 0
523         "1, NaN, 0.5, NaN",
524         "1, 0, 0.5, NaN",
525         "1, -1, 0.5, NaN",
526         // Argument x in [0, 1]
527         "1, 1, NaN, NaN",
528         "1, 1, -1, NaN",
529         "1, 1, 2, NaN",
530     })
531     void testIBetaEdgeCases(double a, double b, double x, double expected) {
532         // Note: Despite being named 'beta' this function is the full incomplete beta
533         Assertions.assertEquals(expected, BoostBeta.beta(a, b, x), "beta");
534         Assertions.assertEquals(expected, BoostBeta.betac(a, b, x), "betac");
535         final Policy pol = Policy.getDefault();
536         Assertions.assertEquals(expected, BoostBeta.beta(a, b, x, pol), "beta");
537         Assertions.assertEquals(expected, BoostBeta.betac(a, b, x, pol), "betac");
538 
539         // The ibetaDerivative has the same invalid domain for a, b and x
540         // as beta. This is despite the ibeta being defined when a or b is 0.
541         // In that case the value x is ignored in ibeta (it is a constant
542         // function) and there is no derivative.
543         Assertions.assertEquals(expected, BoostBeta.ibetaDerivative(a, b, x), "ibetaDerivative");
544     }
545 
546     @ParameterizedTest
547     @CsvSource({
548         // Argument a >= 0
549         "NaN, 1, 0.5, NaN",
550         "-1, 1, 0.5, NaN",
551         // Argument b >= 0
552         "1, NaN, 0.5, NaN",
553         "1, -1, 0.5, NaN",
554         // a and b cannot both be 0
555         "0, 0, 0.5, NaN",
556         // a or b are allowed to be zero, x is ignored
557         "0, 1, 0.25, 1",
558         "0, 1, 0.5, 1",
559         "0, 2, 0.5, 1",
560         "1, 0, 0.25, 0",
561         "1, 0, 0.5, 0",
562         "2, 0, 0.5, 0",
563         // Argument x in [0, 1]
564         "1, 1, NaN, NaN",
565         "1, 1, -1, NaN",
566         "1, 1, 2, NaN",
567     })
568     void testRegularisedIBetaEdgeCases(double a, double b, double x, double expected) {
569         Assertions.assertEquals(expected, BoostBeta.ibeta(a, b, x), "ibeta");
570         Assertions.assertEquals(1 - expected, BoostBeta.ibetac(a, b, x), "ibetac");
571         final Policy pol = Policy.getDefault();
572         Assertions.assertEquals(expected, BoostBeta.ibeta(a, b, x, pol), "ibeta");
573         Assertions.assertEquals(1 - expected, BoostBeta.ibetac(a, b, x, pol), "ibetac");
574     }
575 
576     /**
577      * ibeta derivative spot tests extracted from
578      * {@code boost/libs/math/test/test_ibeta_derivative.hpp}.
579      */
580     @Test
581     void testIBetaDerivativeSpotTests() {
582         final int tolerance = 400;
583         assertClose(BoostBeta::ibetaDerivative, 2, 4, Math.scalb(1.0, -557), 4.23957586190238472641508753637420672781472122471791800210e-167, tolerance * 4);
584         assertClose(BoostBeta::ibetaDerivative, 2, 4.5, Math.scalb(1.0, -557), 5.24647512910420109893867082626308082567071751558842352760e-167, tolerance * 4);
585 
586         // Additions
587         for (final double p : new double[] {3, 13, 42}) {
588             // x==0
589             assertClose(BoostBeta::ibetaDerivative, 2, p, 0, 0.0, 0);
590             assertClose(BoostBeta::ibetaDerivative, 1, p, 0, p, 0);
591             assertClose(BoostBeta::ibetaDerivative, 0.5, p, 0, Double.POSITIVE_INFINITY, 0);
592             // x==1
593             assertClose(BoostBeta::ibetaDerivative, p, 2, 1, 0.0, 0);
594             assertClose(BoostBeta::ibetaDerivative, p, 1, 1, p, 0);
595             assertClose(BoostBeta::ibetaDerivative, p, 0.5, 1, Double.POSITIVE_INFINITY, 0);
596         }
597     }
598 
599     @ParameterizedTest
600     @EnumSource(value = TriTestCase.class, mode = Mode.MATCH_ANY, names = {"IBETA_DERIV.*"})
601     void testIBetaDerivative(TriTestCase tc) {
602         assertFunction(tc);
603     }
604 
605     /**
606      * ibeta spot tests extracted from
607      * {@code boost/libs/math/test/test_ibeta.hpp}.
608      */
609     @Test
610     void testIBetaSpotTests() {
611         int tolerance = 30;
612 
613         // Spot values are from http://functions.wolfram.com/webMathematica/FunctionEvaluation.jsp?name=BetaRegularized
614         // using precision of 50 decimal digits.
615         assertClose(BoostBeta::ibeta, 159.0 / 10000, 1184.0 / 1000000000L, 6917.0 / 10000,
616             0.000075393541456247525676062058821484095548666733251733, tolerance);
617         assertClose(BoostBeta::ibeta, 4243.0 / 100, 3001.0 / 10000, 9157.0 / 10000,
618             0.0028387319012616013434124297160711532419664289474798, tolerance);
619         assertClose(BoostBeta::ibeta, 9713.0 / 1000, 9940.0 / 100, 8391.0 / 100000,
620             0.46116895440368248909937863372410093344466819447476, tolerance * 2);
621         // extra tolerance needed on linux X86EM64
622         assertClose(BoostBeta::ibeta, 72.5, 1.125, 0.75, 1.3423066982487051710597194786268004978931316494920e-9,
623             tolerance * 3);
624         assertClose(BoostBeta::ibeta, 4985.0 / 1000, 1066.0 / 1000, 7599.0 / 10000,
625             0.27533431334486812211032939156910472371928659321347, tolerance);
626         assertClose(BoostBeta::ibeta, 6813.0 / 1000, 1056.0 / 1000, 1741.0 / 10000,
627             7.6736128722762245852815040810349072461658078840945e-6, tolerance);
628         assertClose(BoostBeta::ibeta, 4898.0 / 10000, 2251.0 / 10000, 2003.0 / 10000,
629             0.17089223868046209692215231702890838878342349377008, tolerance);
630         assertClose(BoostBeta::ibeta, 4049.0 / 1000, 1540.0 / 10000, 6537.0 / 10000,
631             0.017273988301528087878279199511703371301647583919670, tolerance);
632         assertClose(BoostBeta::ibeta, 7269.0 / 1000, 1190.0 / 10000, 8003.0 / 10000,
633             0.013334694467796052900138431733772122625376753696347, tolerance);
634         assertClose(BoostBeta::ibeta, 2726.0 / 1000, 1151.0 / 100000, 8665.0 / 100000,
635             5.8218877068298586420691288375690562915515260230173e-6, tolerance);
636         assertClose(BoostBeta::ibeta, 3431.0 / 10000, 4634.0 / 100000, 7582.0 / 10000,
637             0.15132819929418661038699397753916091907278005695387, tolerance);
638 
639         assertClose(BoostBeta::ibeta, 0.34317314624786377, 0.046342257410287857, 0, 0, tolerance);
640         assertClose(BoostBeta::ibetac, 0.34317314624786377, 0.046342257410287857, 0, 1, tolerance);
641         assertClose(BoostBeta::ibeta, 0.34317314624786377, 0.046342257410287857, 1, 1, tolerance);
642         assertClose(BoostBeta::ibetac, 0.34317314624786377, 0.046342257410287857, 1, 0, tolerance);
643         assertClose(BoostBeta::ibeta, 1, 4634.0 / 100000,
644             32.0 / 100, 0.017712849440718489999419956301675684844663359595318, tolerance);
645         assertClose(BoostBeta::ibeta, 4634.0 / 100000,
646             1, 32.0 / 100, 0.94856839398626914764591440181367780660208493234722, tolerance);
647 
648         // try with some integer arguments:
649         assertClose(BoostBeta::ibeta, 3, 8, 0.25, 0.474407196044921875, tolerance);
650         assertClose(BoostBeta::ibeta, 6, 8, 0.25, 0.08021259307861328125, tolerance);
651         assertClose(BoostBeta::ibeta, 12, 1, 0.25, 5.9604644775390625e-8, tolerance);
652         assertClose(BoostBeta::ibeta, 1, 8, 0.25, 0.8998870849609375, tolerance);
653 
654         // very naive check on derivative:
655         tolerance = 100;
656         assertClose(BoostBeta::ibetaDerivative, 2, 3, 0.5,
657             Math.pow(0.5, 2) * Math.pow(0.5, 1) / BoostBeta.beta(2, 3), tolerance);
658 
659         //
660         // Special cases and error handling:
661         //
662         Assertions.assertEquals(1, BoostBeta.ibeta(0, 2, 0.5));
663         Assertions.assertEquals(0, BoostBeta.ibeta(3, 0, 0.5));
664         Assertions.assertEquals(0, BoostBeta.ibetac(0, 2, 0.5));
665         Assertions.assertEquals(1, BoostBeta.ibetac(4, 0, 0.5));
666 
667         // Domain errors
668         Assertions.assertEquals(Double.NaN, BoostBeta.beta(0, 2, 0.5));
669         Assertions.assertEquals(Double.NaN, BoostBeta.beta(3, 0, 0.5));
670         Assertions.assertEquals(Double.NaN, BoostBeta.betac(0, 2, 0.5));
671         Assertions.assertEquals(Double.NaN, BoostBeta.betac(4, 0, 0.5));
672 
673         Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(0, 0, 0.5));
674         Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(-1, 2, 0.5));
675         Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, -2, 0.5));
676         Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, 2, -0.5));
677         Assertions.assertEquals(Double.NaN, BoostBeta.ibetac(2, 2, 1.5));
678 
679         //
680         // a = b = 0.5 is a special case:
681         //
682         assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.25, 1.0 / 3, tolerance);
683         assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.25, 2.0 / 3, tolerance);
684         assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.125, 0.230053456162615885213780567705142893009911395270714102055874, tolerance);
685         assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.125, 0.769946543837384114786219432294857106990088604729285897944125, tolerance);
686         assertClose(BoostBeta::ibeta, 0.5, 0.5, 0.825, 0.725231121519469565327291851560156562956885802608457839260161, tolerance);
687         assertClose(BoostBeta::ibetac, 0.5, 0.5, 0.825, 0.274768878480530434672708148439843437043114197391542160739838, tolerance);
688         // beta(0.5, 0.5) = pi. Multiply by pi for full incomplete beta
689         assertClose(BoostBeta::beta, 0.5, 0.5, 0.25, Math.PI / 3, tolerance);
690         assertClose(BoostBeta::betac, 0.5, 0.5, 0.25, Math.PI * 2 / 3, tolerance);
691         //
692         // Second argument is 1 is a special case, see http://functions.wolfram.com/GammaBetaErf/BetaRegularized/03/01/01/
693         //
694         assertClose(BoostBeta::ibeta, 0.5, 1, 0.825, 0.908295106229247499626759842915458109758420750043003849691665, tolerance);
695         assertClose(BoostBeta::ibetac, 0.5, 1, 0.825, 0.091704893770752500373240157084541890241579249956996150308334, tolerance);
696         assertClose(BoostBeta::ibeta, 30, 1, 0.825, 0.003116150729395132012981654047222541793435357905008020740211, tolerance);
697         assertClose(BoostBeta::ibetac, 30, 1, 0.825, 0.996883849270604867987018345952777458206564642094991979259788, tolerance);
698 
699         //
700         // Bug cases from Rocco Romeo:
701         //
702         assertClose(BoostBeta::beta, 2, 24, Math.scalb(1.0, -52),
703             2.46519032881565349871772482100516780410072110983579277754743e-32, tolerance);
704         assertClose(BoostBeta::ibeta, 2, 24, Math.scalb(1.0, -52),
705             1.47911419728939209923063489260310068246043266590147566652846e-29, tolerance);
706         assertClose(BoostBeta::beta, 3, 2, Math.scalb(1.0, -270),
707             4.88182556606650701438035298707052523938789614661168065734809e-245, tolerance);
708         assertClose(BoostBeta::beta, 2, 31, Math.scalb(1.0, -373),
709             1.35080680244581673116149460571129957689952846520037541640260e-225, tolerance);
710         assertClose(BoostBeta::ibeta, 3, 2, Math.scalb(1.0, -270),
711             5.85819067927980841725642358448463028726547537593401678881771e-244, tolerance);
712         assertClose(BoostBeta::ibeta, 2, 31, Math.scalb(1.0, -373),
713             1.34000034802625019731220264886560918028433223747877241307138e-222, tolerance);
714         //
715         // Bug cases from Rocco Romeo:
716         //
717         assertClose(BoostBeta::beta, 2, 4, Math.scalb(1 + 1.0 / 1024, -351),
718             2.381008060978474962211278613067275529112106932635520021e-212, tolerance);
719         assertClose(BoostBeta::beta, 2, 4, Math.scalb(1 + 1.0 / 2048, -351),
720             2.378685692854274898232669682422430136513931911501225435e-212, tolerance);
721         assertClose(BoostBeta::ibeta, 3, 5, Math.scalb(1 + 15.0 / 16, -268),
722             2.386034198603463687323052353589201848077110231388968865e-240, tolerance);
723         assertClose(BoostBeta::ibetaDerivative, 2, 4, Math.scalb(1.0, -557),
724             4.23957586190238472641508753637420672781472122471791800210e-167, tolerance * 4);
725         assertClose(BoostBeta::ibetaDerivative, 2, 4.5, Math.scalb(1.0, -557),
726             5.24647512910420109893867082626308082567071751558842352760e-167, tolerance * 20);
727 
728         // Additional tests
729 
730         // Underflow of binomialTerm(n, n, x, y) but binomialTerm(n, n - 1, x, y) is non-zero.
731         // Result computed using Maxima: beta_incomplete_regularized with 64 digits precision.
732         // Note this is moderately accurate until the missing first term is a significant.
733         tolerance = 5;
734         assertClose(BoostBeta::ibeta, 20, 2, Math.scalb(1.0, -52),
735             1.78247646441082836775138741451452643650923455144048771440215391e-312, tolerance);
736         assertClose(BoostBeta::ibeta, 33, 2, Math.scalb(1.0, -32),
737             4.403555717560735620179800985566548469434721409459606451475482401e-317, tolerance);
738         assertClose(BoostBeta::ibeta, 759, 2, 0.375,
739             2.327049191912271223071639014409322663839431670313469640444508565e-321, tolerance);
740 
741         // With integer a and/or b just above 1
742         // ibeta_small_int_data uses 5 as the smallest parameter above 1
743         tolerance = 1;
744         assertClose(BoostBeta::ibeta, 1, 2, 0.125, 0.234375, tolerance);
745         assertClose(BoostBeta::ibeta, 1, 2, 0.5, 0.75, tolerance);
746         assertClose(BoostBeta::ibeta, 1, 2, 0.75, 0.9375, tolerance);
747         assertClose(BoostBeta::ibeta, 2, 1, 0.125, 0.015625, tolerance);
748         assertClose(BoostBeta::ibeta, 2, 1, 0.5, 0.25, tolerance);
749         assertClose(BoostBeta::ibeta, 2, 1, 0.75, 0.5625, tolerance);
750         assertClose(BoostBeta::ibeta, 2, 2, 0.125, 0.04296875, tolerance);
751         assertClose(BoostBeta::ibeta, 2, 2, 0.5, 0.5, tolerance);
752         assertClose(BoostBeta::ibeta, 2, 2, 0.75, 0.84375, tolerance);
753         assertClose(BoostBeta::ibeta, 2, 3, 0.125, 0.078857421875, tolerance);
754         assertClose(BoostBeta::ibeta, 2, 3, 0.5, 0.6875, tolerance);
755         assertClose(BoostBeta::ibeta, 2, 3, 0.75, 0.94921875, tolerance);
756 
757         // Extreme integer arguments. Creates an overflow in the binomial coefficient
758         // binomial(2147483545 + 39, 38) ~ 7.84899e309
759         Assertions.assertEquals(1, BoostBeta.ibeta(39, 2147483546, Math.nextDown(1.0)));
760         Assertions.assertEquals(0, BoostBeta.ibetac(39, 2147483546, Math.nextDown(1.0)));
761         // Executes a different code path not using the binomial
762         Assertions.assertEquals(1, BoostBeta.ibeta(39, Integer.MAX_VALUE, Math.nextDown(1.0)));
763         Assertions.assertEquals(0, BoostBeta.ibetac(39, Integer.MAX_VALUE, Math.nextDown(1.0)));
764 
765         // x==0 or 1
766         Assertions.assertEquals(0, BoostBeta.ibeta(2, 4, 0));
767         Assertions.assertEquals(1, BoostBeta.ibeta(2, 4, 1));
768         Assertions.assertEquals(1, BoostBeta.ibetac(2, 4, 0));
769         Assertions.assertEquals(0, BoostBeta.ibetac(2, 4, 1));
770         Assertions.assertEquals(0, BoostBeta.beta(2, 4, 0));
771         TestUtils.assertEquals(0.05, BoostBeta.beta(2, 4, 1), 1);
772         TestUtils.assertEquals(0.05, BoostBeta.betac(2, 4, 0), 1);
773         Assertions.assertEquals(0, BoostBeta.betac(2, 4, 1));
774 
775         tolerance = 30;
776 
777         // a > 15, integer b < 40, b * x >= 0.7
778         assertClose(BoostBeta::ibeta, 72.5, 2, 0.75, 1.673181444858556263e-8, tolerance * 2);
779 
780         // a < 15, integer b < 40, b * x >= 0.7, regularized
781         assertClose(BoostBeta::ibeta, 14.5, 2, 0.75, 7.1367429756558048437e-2, tolerance);
782     }
783 
784     @ParameterizedTest
785     @EnumSource(value = TriTestCase.class, mode = Mode.MATCH_ANY,
786                 names = {"IBETA_[SML].*", "IBETAC_[SML].*", "RBETA.*"})
787     void testIBeta(TriTestCase tc) {
788         assertFunction(tc);
789     }
790 
791     /**
792      * Test the incomplete beta function uses the policy containing the epsilon and
793      * maximum iterations for series evaluations. The data targets methods computed
794      * using a series component to check the policy is not ignored.
795      *
796      * <p>This does not target
797      * {@link BoostBeta#ibetaFraction(double, double, double, double, Policy, boolean)}.
798      * This is used when the result is sub-normal and the continued
799      * fraction converges without iteration. The policy has no effect.
800      *
801      * <p>Running the policy tests on their own should hit the code paths
802      * using the policy for function evaluation:
803      * <pre>
804      * mvn clean test -Dtest=BoostBetaTest#testIBetaPolicy* jacoco:report
805      * </pre>
806      */
807     @ParameterizedTest
808     @CsvSource(value = {
809         // Data extracted from the resource files and formatted to double precision
810 
811         // Target ibetaSeries
812         "4.201121919322759E-5,2.1881177963223308E-4,0.6323960423469543,23803.707603529016,4569.595256369948,0.838947362634037,0.16105263736596298",
813         "0.22512593865394592,0.4898320138454437,0.7996731996536255,4.764013990849158,0.9820281524501243,0.8290948573018541,0.17090514269814597",
814         "4.623167842510156E-5,4.340034502092749E-5,0.135563462972641,21628.337679706918,23043.143905699715,0.48416432390665337,0.5158356760933467",
815         "2.9415024982881732E-5,4.1924233664758503E-4,0.3082362115383148,33995.42298781772,2386.0630988783787,0.9344154580933705,0.0655845419066295",
816         "1.184685606858693E-5,0.015964560210704803,0.3082362115383148,84409.7658651171,63.42758275377908,0.9992491395179357,7.508604820642986E-4",
817         "3.529437162796967E-5,2.2326681573758833E-5,0.6323960423469543,28333.671885777032,44788.91695876111,0.3874817937042091,0.6125182062957909",
818         "0.06715317070484161,2.306319236755371,0.9084427952766418,13.787272071732604,0.001859217475218142,0.999865167903893,1.3483209610697333E-4",
819         "0.3183284401893616,3.165504217147827,0.07764927297830582,1.3374998709679642,0.6794195418585712,0.6631399660602084,0.3368600339397915",
820         "0.15403440594673157,4.049813747406006,0.34629878401756287,4.872033861103044,0.08561968850485947,0.9827297959310547,0.01727020406894529",
821         "1.3317101001739502,0.7650398015975952,0.6445860862731934,0.47144799136487586,0.5594135526519237,0.4573339592510717,0.5426660407489283",
822         "0.11902070045471191,7.269547462463379,0.19963125884532928,6.225047194692518,0.08420413075357451,0.9866538632858122,0.013346136714187858",
823         "2.664715051651001,0.6914005279541016,0.8443243503570557,0.3338388990912521,0.3587830340198169,0.4819929648946269,0.518007035105373",
824         "1.0562920570373535,6.812713623046875,0.8258343935012817,0.12732498812245932,9.807107058749213E-7,0.9999922976379849,7.702362015088557E-6",
825         "1.7118667364120483,3.0191311836242676,0.07594671100378036,0.0064151684204504875,0.10850933283432233,0.05582072012850319,0.9441792798714969",
826         // Target betaSmallBLargeASeries
827         "0.04634225741028786,0.34317314624786377,0.24176712334156036,20.363670894714268,3.6307737402387885,0.8486827348798152,0.15131726512018484",
828         "2.113992923113983E-5,1.7535277947899885E-5,0.8350250720977783,47305.46963235176,57026.27394012006,0.4534139659948463,0.5465860340051537",
829         "4.005068331025541E-5,42.84983825683594,0.12707412242889404,24964.03974453176,4.764518849491958E-4,0.9999999809144722,1.9085527852527327E-8",
830         "67.90167999267578,0.8324270844459534,0.9676981568336487,0.002669338211636283,0.031098353139101195,0.0790500654578503,0.9209499345421497",
831         "0.395370751619339,0.004023698624223471,0.9058013558387756,4.307485901473997,246.2348442100959,0.017192647244702382,0.9828073527552976",
832         "3.444607973098755,66.36054992675781,0.09654488414525986,1.4741027361579568E-6,8.307589573110104E-8,0.9466497330301025,0.05335026696989754",
833         "1.0665277242660522,4.985442161560059,0.2400285303592682,0.12523918055824373,0.0476465037156954,0.7244045745268357,0.2755954254731643",
834         // Target ibetaFraction2
835         "1.319732904434204,4.903014659881592,0.33251503109931946,0.0837704419861451,0.021604794441302123,0.7949727547593459,0.20502724524065405",
836         "485.7690734863281,190.16734313964844,0.6323960423469543,7.8023253024461E-182,7.885435919806278E-176,9.894592590329194E-7,0.9999990105407409",
837     })
838     void testIBetaPolicy(double a, double b, double x, double beta, double betac, double ibeta, double ibetac) {
839         // Low iterations should fail to converge
840         final Policy pol1 = new Policy(0x1.0p-52, 1);
841         Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.beta(a, b, x, pol1), "beta");
842         Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.betac(a, b, x, pol1), "betac");
843         Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.ibeta(a, b, x, pol1), "ibeta");
844         Assertions.assertThrows(ArithmeticException.class, () -> BoostBeta.ibetac(a, b, x, pol1), "ibetac");
845 
846         // Low epsilon should not be as accurate
847         final Policy pol2 = new Policy(1e-3, Integer.MAX_VALUE);
848 
849         // Ignore infinite
850         if (Double.isFinite(beta)) {
851             final double u1 = BoostBeta.beta(a, b, x);
852             final double u2 = BoostBeta.beta(a, b, x, pol2);
853             assertCloser("beta", beta, u1, u2);
854         }
855         if (Double.isFinite(betac)) {
856             final double l1 = BoostBeta.betac(a, b, x);
857             final double l2 = BoostBeta.betac(a, b, x, pol2);
858             assertCloser("betac", betac, l1, l2);
859         }
860 
861         // Ignore 0 or 1
862         if ((int) ibeta != ibeta) {
863             final double p1 = BoostBeta.ibeta(a, b, x);
864             final double p2 = BoostBeta.ibeta(a, b, x, pol2);
865             assertCloser("ibeta", ibeta, p1, p2);
866         }
867         if ((int) ibetac != ibetac) {
868             final double q1 = BoostBeta.ibetac(a, b, x);
869             final double q2 = BoostBeta.ibetac(a, b, x, pol2);
870             assertCloser("ibetac", ibetac, q1, q2);
871         }
872     }
873 
874     /**
875      * Assert x is closer to the expected result than y.
876      */
877     private static void assertCloser(String msg, double expected, double x, double y) {
878         if (!Double.isFinite(expected)) {
879             // Test both answers are correct
880             Assertions.assertEquals(expected, x);
881             Assertions.assertEquals(expected, y);
882         } else {
883             final double dx = Math.abs(expected - x);
884             final double dy = Math.abs(expected - y);
885             Assertions.assertTrue(dx < dy,
886                 () -> String.format("%s %s : %s (%s) : %s (%s)", msg, expected,
887                     x, dx / Math.ulp(expected), y, dy / Math.ulp(expected)));
888         }
889     }
890 
891     /**
892      * Computes the binomial coefficient.
893      *
894      * <p>Wrapper to convert double arguments to integer and call the
895      * {@link BoostBeta#binomialCoefficient(int, int)} method.
896      *
897      * @param n Size of the set.
898      * @param k Size of the subsets to be counted.
899      * @return {@code n choose k}.
900      */
901     private static double binomialCoefficient(double n, double k) {
902         return BoostBeta.binomialCoefficient((int) n, (int) k);
903     }
904 
905     /**
906      * Computes the binomial coefficient using the beta function.
907      *
908      * <p>Adapted from {@code <boost/math/special_functions/binomial.hpp>}.
909      *
910      * @param n1 Size of the set.
911      * @param k1 Size of the subsets to be counted.
912      * @return {@code n choose k}.
913      */
914     private static double binomialCoefficient1(double n1, double k1) {
915         // Use symmetry
916         int n = (int) n1;
917         int k = (int) k1;
918         final int m = Math.min(k, n - k);
919         if (m == 0) {
920             return 1;
921         }
922         if (m == 1) {
923             return n;
924         }
925         if (m == 2) {
926             // Cannot overflow a long
927             return (n - 1L) * n / 2;
928         }
929 
930         double result;
931         if (n <= 170) {
932             // Use fast table lookup:
933             // Note: This has lower error on test data than calling BinomialCoefficientDouble.
934             result = BoostGamma.uncheckedFactorial(n);
935             // Smaller m will have a more accurate factorial
936             result /= BoostGamma.uncheckedFactorial(m);
937             result /= BoostGamma.uncheckedFactorial(n - m);
938         } else {
939             if (k < n - k) {
940                 result = k * BoostBeta.beta(k, n - k + 1);
941             } else {
942                 result = (n - k) * BoostBeta.beta(k + 1, n - k);
943             }
944             result = 1 / result;
945         }
946         return Math.ceil(result - 0.5f);
947     }
948 
949     /**
950      * Beta function.
951      * <p>\[ B(p, q) = \frac{\Gamma(p) \Gamma(q)}}{\Gamma(p+q)} \]
952      *
953      * <p>Computed using {@link LogBeta}.
954      *
955      * @param p Argument p
956      * @param q Argument q
957      * @return beta value
958      */
959     private static double beta(double p, double q) {
960         return Math.exp(LogBeta.value(p, q));
961     }
962 
963     /**
964      * Derivative of the regularised incomplete beta.
965      * <p>\[ \frac{\delta}{\delta x} I_x(a, b) = \frac{(1-x)^{b-1} x^{a-1}}{\B(a, b)} \]
966      *
967      * <p>Computed using {@link LogGamma}.
968      *
969      * @param a Argument a
970      * @param b Argument b
971      * @param x Argument x
972      * @return ibeta derivative
973      */
974     private static double ibetaDerivative1(double a, double b, double x) {
975         if (x < 0 || x > 1) {
976             return 0;
977         } else if (x == 0) {
978             if (a < 1) {
979                 // Distribution is not valid when x=0, alpha<1
980                 // due to a divide by zero error.
981                 // Do not raise an exception and return the limit.
982                 return Double.POSITIVE_INFINITY;
983             }
984             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b)
985             if (a == 1) {
986                 return b;
987             }
988             return 0;
989         } else if (x == 1) {
990             if (b < 1) {
991                 // Distribution is not valid when x=1, beta<1
992                 // due to a divide by zero error.
993                 // Do not raise an exception and return the limit.
994                 return Double.POSITIVE_INFINITY;
995             }
996             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b)
997             if (b == 1) {
998                 return a;
999             }
1000             return 0;
1001         } else {
1002             final double z = LogGamma.value(a) + LogGamma.value(b) - LogGamma.value(a + b);
1003             final double logX = Math.log(x);
1004             final double log1mX = Math.log1p(-x);
1005             return Math.exp((a - 1) * logX + (b - 1) * log1mX - z);
1006         }
1007     }
1008 
1009     /**
1010      * Derivative of the regularised incomplete beta.
1011      * <p>\[ \frac{\delta}{\delta x} I_x(a, b) = \frac{(1-x)^{b-1} x^{a-1}}{\B(a, b)} \]
1012      *
1013      * <p>Computed using {@link LogBeta}.
1014      *
1015      * @param a Argument a
1016      * @param b Argument b
1017      * @param x Argument x
1018      * @return ibeta derivative
1019      */
1020     private static double ibetaDerivative2(double a, double b, double x) {
1021         if (x < 0 || x > 1) {
1022             return 0;
1023         } else if (x == 0) {
1024             if (a < 1) {
1025                 // Distribution is not valid when x=0, alpha<1
1026                 // due to a divide by zero error.
1027                 // Do not raise an exception and return the limit.
1028                 return Double.POSITIVE_INFINITY;
1029             }
1030             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b)
1031             if (a == 1) {
1032                 return b;
1033             }
1034             return 0;
1035         } else if (x == 1) {
1036             if (b < 1) {
1037                 // Distribution is not valid when x=1, beta<1
1038                 // due to a divide by zero error.
1039                 // Do not raise an exception and return the limit.
1040                 return Double.POSITIVE_INFINITY;
1041             }
1042             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b)
1043             if (b == 1) {
1044                 return a;
1045             }
1046             return 0;
1047         } else {
1048             final double z = LogBeta.value(a, b);
1049             final double logX = Math.log(x);
1050             final double log1mX = Math.log1p(-x);
1051             return Math.exp((a - 1) * logX + (b - 1) * log1mX - z);
1052         }
1053     }
1054 
1055     /**
1056      * Regularised incomplete beta.
1057      * Helper method to call the classic continued fraction representation.
1058      *
1059      * @param a Argument a
1060      * @param b Argument b
1061      * @param x Argument x
1062      * @return p
1063      */
1064     private static double ibeta(double a, double b, double x) {
1065         return ibetaImp(a, b, x, false);
1066     }
1067 
1068     /**
1069      * Complement of the regularised incomplete beta.
1070      * Helper method to call the classic continued fraction representation.
1071      *
1072      * @param a Argument a
1073      * @param b Argument b
1074      * @param x Argument x
1075      * @return q
1076      */
1077     private static double ibetac(double a, double b, double x) {
1078         return ibetaImp(a, b, x, true);
1079     }
1080 
1081     /**
1082      * Regularised incomplete beta implementation using
1083      * the classic continued fraction representation.
1084      *
1085      * <p>This is a partial implementation with no special case handling.
1086      *
1087      * @param a Argument a
1088      * @param b Argument b
1089      * @param x Argument x
1090      * @param inv true to compute the complement value
1091      * @return p
1092      */
1093     private static double ibetaImp(double a, double b, double x, boolean inv) {
1094         // This logic is from Commons Numbers 1.0 for use of the
1095         // complement of the regularised beta function
1096         double result;
1097         if (x > (a + 1) / (2 + b + a)) {
1098             result = BoostBeta.ibetaFraction(b, a, 1 - x, x, Policy.getDefault(), true);
1099             inv = !inv;
1100         } else {
1101             result = BoostBeta.ibetaFraction(a, b, x, 1 - x, Policy.getDefault(), true);
1102         }
1103         return inv ? 1 - result : result;
1104     }
1105 
1106     /**
1107      * Assert the function is close to the expected value.
1108      *
1109      * @param fun Function
1110      * @param x Input value
1111      * @param y Input value
1112      * @param expected Expected value
1113      * @param tolerance the tolerance
1114      */
1115     private static void assertClose(DoubleBinaryOperator fun, double x, double y, double expected, int tolerance) {
1116         final double actual = fun.applyAsDouble(x, y);
1117         TestUtils.assertEquals(expected, actual, tolerance, null, () -> x + ", " + y);
1118     }
1119 
1120     /**
1121      * Assert the function is close to the expected value.
1122      *
1123      * @param fun Function
1124      * @param x Input value
1125      * @param y Input value
1126      * @param expected Expected value
1127      * @param eps Relative tolerance
1128      */
1129     private static void assertWithinEps(DoubleBinaryOperator fun, double x, double y, double expected, double eps) {
1130         final double actual = fun.applyAsDouble(x, y);
1131         Assertions.assertEquals(expected, actual, Math.abs(expected) * eps, () -> x + ", " + y);
1132     }
1133 
1134     /**
1135      * Assert the function is close to the expected value.
1136      *
1137      * @param fun Function
1138      * @param x Input value
1139      * @param y Input value
1140      * @param z Input value
1141      * @param expected Expected value
1142      * @param tolerance the tolerance
1143      */
1144     private static void assertClose(DoubleTernaryOperator fun, double x, double y, double z, double expected, int tolerance) {
1145         final double actual = fun.applyAsDouble(x, y, z);
1146         TestUtils.assertEquals(expected, actual, tolerance, null, () -> x + ", " + y + ", " + z);
1147     }
1148 
1149     /**
1150      * Assert the function using extended precision.
1151      *
1152      * @param tc Test case
1153      */
1154     private static void assertFunction(BiTestCase tc) {
1155         final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1156         try (DataReader in = new DataReader(tc.getFilename())) {
1157             while (in.next()) {
1158                 try {
1159                     final double x = in.getDouble(0);
1160                     final double y = in.getDouble(1);
1161                     final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1162                     final double actual = tc.getFunction().applyAsDouble(x, y);
1163                     TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1164                         () -> tc + " x=" + x + ", y=" + y);
1165                 } catch (final NumberFormatException ex) {
1166                     Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1167                 }
1168             }
1169         } catch (final IOException ex) {
1170             Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1171         }
1172 
1173         assertRms(tc, stats);
1174     }
1175 
1176     /**
1177      * Assert the function using extended precision.
1178      *
1179      * @param tc Test case
1180      */
1181     private static void assertFunction(TriTestCase tc) {
1182         final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
1183         try (DataReader in = new DataReader(tc.getFilename())) {
1184             while (in.next()) {
1185                 try {
1186                     final double x = in.getDouble(0);
1187                     final double y = in.getDouble(1);
1188                     final double z = in.getDouble(2);
1189                     final BigDecimal expected = in.getBigDecimal(tc.getExpectedField());
1190                     final double actual = tc.getFunction().applyAsDouble(x, y, z);
1191                     TestUtils.assertEquals(expected, actual, tc.getTolerance(), stats::add,
1192                         () -> tc + " x=" + x + ", y=" + y + ", z=" + z);
1193                 } catch (final NumberFormatException ex) {
1194                     Assertions.fail("Failed to load data: " + Arrays.toString(in.getFields()), ex);
1195                 }
1196             }
1197         } catch (final IOException ex) {
1198             Assertions.fail("Failed to load data: " + tc.getFilename(), ex);
1199         }
1200 
1201         assertRms(tc, stats);
1202     }
1203 
1204     /**
1205      * Assert the Root Mean Square (RMS) error of the function is below the allowed
1206      * maximum for the specified TestError.
1207      *
1208      * @param te Test error
1209      * @param stats Error statistics
1210      */
1211     private static void assertRms(TestError te, TestUtils.ErrorStatistics stats) {
1212         final double rms = stats.getRMS();
1213         //debugRms(te.toString(), stats.getMaxAbs(), rms, stats.getMean(), stats.size());
1214         Assertions.assertTrue(rms <= te.getRmsTolerance(),
1215             () -> String.format("%s RMS %s < %s", te, rms, te.getRmsTolerance()));
1216     }
1217 
1218     /**
1219      * Output the maximum and RMS ulp for the named test. Used for reporting the
1220      * errors and setting appropriate test tolerances. This is relevant across
1221      * different JDK implementations where the java.util.Math functions used in
1222      * BoostGamma may compute to different accuracy.
1223      *
1224      * @param name Test name
1225      * @param maxAbsUlp Maximum |ulp|
1226      * @param rmsUlp RMS ulp
1227      * @param meanUlp Mean ulp
1228      * @param size Number of measurements
1229      */
1230     private static void debugRms(String name, double maxAbsUlp, double rmsUlp, double meanUlp, int size) {
1231         // CHECKSTYLE: stop regexp
1232         System.out.printf("%-35s   max %10.6g   RMS %10.6g   mean %14.6g  n %4d%n",
1233             name, maxAbsUlp, rmsUlp, meanUlp, size);
1234     }
1235 }