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.math.BigDecimal;
20  import java.util.function.DoubleUnaryOperator;
21  import org.junit.jupiter.api.Assertions;
22  import org.junit.jupiter.api.MethodOrderer;
23  import org.junit.jupiter.api.Order;
24  import org.junit.jupiter.api.Test;
25  import org.junit.jupiter.api.TestMethodOrder;
26  import org.junit.jupiter.params.ParameterizedTest;
27  import org.junit.jupiter.params.provider.CsvFileSource;
28  import org.junit.jupiter.params.provider.CsvSource;
29  
30  /**
31   * Tests for {@link BoostErf}.
32   *
33   * <p>Note: Some resource data files used in these tests have been extracted
34   * from the Boost test files for the Erf functions.
35   */
36  @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
37  class BoostErfTest {
38      // Test order:
39      // Any methods not annotated with @Order will be executed as if using int max value.
40      // This class uses the @Order annotation to build the RMS values before asserting
41      // the max RMS. For convenience when using reporting the ulp errors this is done
42      // in the order of the class methods.
43  
44      /**
45       * Define the function to test.
46       * This is a utility to identify if the function is an odd function: f(x) = -f(-x).
47       */
48      private enum TestFunction {
49          ERF(BoostErf::erf, true),
50          ERFC(BoostErf::erfc, false),
51          ERF_INV(BoostErf::erfInv, true),
52          ERFC_INV(BoostErf::erfcInv, false),
53          ERFCX(BoostErf::erfcx, false);
54  
55          /** The function. */
56          private final DoubleUnaryOperator fun;
57  
58          /** Odd function flag: f(x) = -f(-x). */
59          private final boolean odd;
60  
61          /**
62           * @param fun function to test
63           * @param odd true if an odd function
64           */
65          TestFunction(DoubleUnaryOperator fun, boolean odd) {
66              this.fun = fun;
67              this.odd = odd;
68          }
69  
70          /**
71           * @return the function
72           */
73          DoubleUnaryOperator getFunction() {
74              return fun;
75          }
76  
77          /**
78           * @return true if an odd function: f(x) = -f(-x)
79           */
80          boolean isOdd() {
81              return odd;
82          }
83      }
84  
85      /**
86       * Define the test cases for each resource file.
87       * This encapsulates the function to test, the expected maximum and RMS error, and
88       * methods to accumulate error and compute RMS error.
89       *
90       * <h2>Note on accuracy</h2>
91       *
92       * <p>The Boost functions use the default policy of internal promotion
93       * of double to long double if it offers more precision. Code comments
94       * in the implementations for the maximum error are using the defaults with
95       * promotion enabled where the error is 'effectively zero'. Java does not
96       * support long double computation. Tolerances have been set to allow tests to
97       * pass. Spot checks on larger errors have been verified against the reference
98       * implementation compiled with promotion of double <strong>disabled</strong>.
99       *
100      * <p>For the erf and erfc functions the Boost error is due to the accuracy of
101      * exponentiation. From the Boost test_erf.cpp:
102      * <pre>
103      * "On MacOS X erfc has much higher error levels than
104      * expected: given that the implementation is basically
105      * just a rational function evaluation combined with
106      * exponentiation, we conclude that exp and pow are less
107      * accurate on this platform, especially when the result
108      * is outside the range of a double."
109      * </pre>
110      *
111      * @see <a href="https://www.boost.org/doc/libs/1_77_0/libs/math/doc/html/math_toolkit/relative_error.html">Relative error</a>
112      * @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>
113      */
114     private enum TestCase {
115         /** Erf Boost data. */
116         ERF(TestFunction.ERF, 1.3, 0.2),
117         /** Erfc Boost data. */
118         ERFC(TestFunction.ERFC, 2.2, 0.5),
119         /** Erf Boost large data (simple case where all z>8, p=1.0). */
120         ERF_LARGE(TestFunction.ERF, 0, 0.0),
121         /** Erfc Boost large data. */
122         ERFC_LARGE(TestFunction.ERFC, 2.0, 0.7),
123         /** Erf Boost small data (no exponentiation required). */
124         ERF_SMALL(TestFunction.ERF, 1.2, 0.25),
125         /** Erfc Boost small data (no exponentiation required: ulp=0). */
126         ERFC_SMALL(TestFunction.ERFC, 0, 0.0),
127         /** Inverse Erf Boost data. */
128         ERF_INV(TestFunction.ERF_INV, 1.8, 0.65),
129         /** Inverse Erfc Boost data. */
130         ERFC_INV(TestFunction.ERFC_INV, 1.8, 0.65),
131         /** Inverse Erfc Boost big data. */
132         ERFC_INV_BIG(TestFunction.ERFC_INV, 1.8, 0.5),
133         /** Inverse Erf limit data. */
134         ERF_INV_LIMIT(TestFunction.ERF_INV, 1.4, 0.5),
135         /** Inverse Erfc limit data. */
136         ERFC_INV_LIMIT(TestFunction.ERFC_INV, 1.2, 0.5),
137         /** Erfcx negative medium data. */
138         ERFCX_NEG_MEDIUM(TestFunction.ERFCX, 1.7, 0.55),
139         /** Erfcx negative small data. */
140         ERFCX_NEG_SMALL(TestFunction.ERFCX, 0.7, 0.061),
141         /** Erfcx small data. */
142         ERFCX_SMALL(TestFunction.ERFCX, 0.6, 0.073),
143         /** Erfcx medium data. */
144         ERFCX_MED(TestFunction.ERFCX, 1.2, 0.5),
145         /** Erfcx large data. */
146         ERFCX_LARGE(TestFunction.ERFCX, 1.1, 0.45),
147         /** Erfcx huge data. */
148         ERFCX_HUGE(TestFunction.ERFCX, 1.25, 0.45);
149 
150         /** Sum of the squared ULP error and count n. */
151         private final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
152 
153         /** The function. */
154         private final TestFunction fun;
155 
156         /** The maximum allowed ulp. */
157         private final double maxUlp;
158 
159         /** The maximum allowed RMS ulp. */
160         private final double rmsUlp;
161 
162         /**
163          * @param fun function to test
164          * @param maxUlp maximum allowed ulp
165          * @param rmsUlp maximum allowed RMS ulp
166          */
167         TestCase(TestFunction fun, double maxUlp, double rmsUlp) {
168             this.fun = fun;
169             this.maxUlp = maxUlp;
170             this.rmsUlp = rmsUlp;
171         }
172 
173         /**
174          * @return function to test
175          */
176         DoubleUnaryOperator getFunction() {
177             return fun.getFunction();
178         }
179 
180         /**
181          * @return true if an odd function
182          */
183         boolean isOdd() {
184             return fun.isOdd();
185         }
186 
187         /**
188          * @param ulp error in ulp
189          */
190         void addError(double ulp) {
191             stats.add(ulp);
192         }
193 
194         /**
195          * @return Root Mean Squared measured error
196          */
197         double getRMSError() {
198             return stats.getRMS();
199         }
200 
201         /**
202          * @return maximum absolute measured error
203          */
204         double getMaxAbsError() {
205             return stats.getMaxAbs();
206         }
207 
208         /**
209          * @return maximum allowed absolute error
210          */
211         double getTolerance() {
212             return maxUlp;
213         }
214 
215         /**
216          * @return maximum allowed RMS error
217          */
218         double getRmsTolerance() {
219             return rmsUlp;
220         }
221     }
222 
223     @ParameterizedTest
224     @CsvSource({
225         "0, 1",
226         // Reference data from GCC libquadmath expq(z*z)
227         // Cases from known max ulp errors for:
228         // a = z*z; b = z*z round-off
229         // exp(a+b) = exp(a) * exp(b) = exp(a) * expm1(b) + exp(a)
230         "0.042876182518572857, 1.00184005785999452616485960307584103",
231         "0.12332761808971203,  1.01532595755115384513226892929687518",
232         "0.23492402237866017,  1.05674063282543772769083554593703693",
233         "0.5005007866910639,   1.28466892274154983265904587935363446",
234         "0.6079329058333609,   1.44713019294681595655835657289687865",
235         "0.70256943723915932,  1.6382093968128949474570450121807659",
236         "0.82241395271131823,  1.96671514042639717092581737392007089",
237         "1.1676941591314409,   3.90989159781783456283136476225348036",
238         "1.2872328693421708,   5.24339117555116565213868516013500793",
239         "1.763493621168525,    22.4190210354395450203564424019491017",
240         "2.2911777554159483,   190.470153340296021320752950876036293",
241         "2.6219566826446719,   967.443326246348231629933262201707317",
242         "3.0037511703473512,   8287.64469163961117832508854092931197",
243         "4.9612493700241904,   48946578790.7631709834358118976268355",
244         "9.3076562180806182,   4.20727779143532487527563444483221012e+37",
245         "14.323050579907544,   1.24570874136900582743589942668130243e+89",
246         "19.789244604728378,   1.19093202729437857229577485425178318e+170",
247         "25.264725343479071,   1.63276667418921707391087152900497326e+277",
248         "26.471453083170552,   2.12115354204783587764203142231215649e+304",
249         "26.628235718509234,   8.7522773171090885282464768100291983e+307",
250         // Limit.
251         // The next double up (26.64174755704633) has
252         // exp(x*x) = 1.79769313486244732925408679719502907e+308 = Infinity
253         // This is computed as NaN due to lack of overflow checks.
254         "26.641747557046327,   1.79769313486210702414246567679413232e+308",
255     })
256     void testExpxx(double z, BigDecimal expected) {
257         final double actual = BoostErf.expxx(z);
258         TestUtils.assertEquals(expected, actual, 1.0);
259     }
260 
261     @ParameterizedTest
262     @CsvSource({
263         // Known cases where exp(x*x) is infinite and the round-off is zero or negative
264         "27,                 3.98728526204259656354686104733435016e+316",
265         "27.45162436,        1.79769313486244732925408679719502907e+308",
266         // Next double after 26.641747557046327
267         "26.64174755704633,  1.79769313486244732925408679719502907e+308",
268     })
269     void testExpxxOverflow(double z, double expected) {
270         final double e = Math.exp(z * z);
271         Assertions.assertEquals(Double.POSITIVE_INFINITY, e);
272         Assertions.assertEquals(Double.POSITIVE_INFINITY, expected);
273         Assertions.assertEquals(Double.NaN, BoostErf.expxx(z), "No overflow checks");
274     }
275 
276     @ParameterizedTest
277     @CsvSource({
278         "0, 1",
279         // Reference data from GCC libquadmath expq(-z*z)
280         // Cases from known max ulp errors for:
281         // a = z*z; b = z*z round-off
282         // exp(a+b) = exp(a) * exp(b) = exp(a) * expm1(b) + exp(a)
283         "0.018888546570790723, 0.999643286445856926713199201695735708",
284         "0.044040879540028978, 0.998062280736064566626960146268218869",
285         "0.14252706610502067,  0.979890973955195922461144945194213951",
286         "0.21644880196960639,  0.954230441398827896658935167789523746",
287         "0.38611178653477424,  0.861498200598095608584361373395644347",
288         "0.48918821106359167,  0.78717467413707227187574762653633039",
289         "0.69762286071744906,  0.614665134758267412066486574992456239",
290         "0.74212965000887676,  0.576513560506426682539710563714000483",
291         "0.83549504832885912,  0.497553606822144444881962542058093636",
292         "0.93551572137564831,  0.416782963065511995785537520046193978",
293         "1.1557980830683314,   0.262929535430060880774643925549236367",
294         "1.4043799106718902,   0.139138848619574074162424456941838826",
295         "1.76928217755289,     0.0437020868591733321664690441540917611",
296         "3.030043930000375,    0.000102960388874918024753918581478636755",
297         "6.8941333932059292,   2.28236391243884653067874490191517352e-21",
298         "12.150966601450184,   7.55373161692552909052687379129491314e-65",
299         "18.145618649215113,   1.00621134630738394627962504745954853e-143",
300         "22.58677686154909,    2.74945208846365015258084878726170577e-222",
301         "25.307715003447811,   6.96433586576952601048539830338695099e-279",
302         "26.314662520770881,   1.85270995749302588464727454552962812e-301",
303         "26.550566379026709,   7.1067746279803211201902681713374704e-307",
304         // Limit
305         "27.297128403953796,   2.47032822920651974943004172315065178e-324",
306         // exp(-z*z) = 0
307         "27.2971284039538,     2.47032822920604061009296400001395168e-324",
308     })
309     void testExpmxx(double z, BigDecimal expected) {
310         final double actual = BoostErf.expmxx(z);
311         TestUtils.assertEquals(expected, actual, 1.0);
312     }
313 
314     @ParameterizedTest
315     @CsvSource({
316         // Reference data from GCC libquadmath expq(-z*z)
317         // exp(z) is nearly sub-normal
318         "26.589967471163597, 8.75686990774305433076998380040492953e-308",
319         "26.592726991055095, 7.56157741629803260538530412232841961e-308",
320         "26.593224983876986, 7.36392883240998076191982506525092463e-308",
321         "26.596608821043414, 6.15095812619805721349676447899566968e-308",
322     })
323     void testExpmxxCloseToSubNormal(double z, BigDecimal expected) {
324         final double actual = BoostErf.expmxx(z);
325         TestUtils.assertEquals(expected, actual, 1.3);
326     }
327 
328     @ParameterizedTest
329     @CsvSource({
330         "-Infinity, -1",
331         "Infinity, 1",
332         "0, 0",
333         // Odd function
334         "-0.0, -0.0",
335         "NaN, NaN",
336         // Realistic limits
337         "-6, -1",
338         "6, 1",
339     })
340     void testErfEdgeCases(double z, double p) {
341         Assertions.assertEquals(p, BoostErf.erf(z));
342     }
343 
344     @ParameterizedTest
345     @CsvSource({
346         "-Infinity, 2",
347         "Infinity, 0",
348         "0, 1",
349         "NaN, NaN",
350         // Realistic limits
351         "-6, 2",
352         "28, 0",
353     })
354     void testErfcEdgeCases(double z, double q) {
355         Assertions.assertEquals(q, BoostErf.erfc(z));
356     }
357 
358     @ParameterizedTest
359     @Order(1)
360     @CsvFileSource(resources = "erf_data.csv")
361     void testErf(double z, BigDecimal p, double q) {
362         assertErf(TestCase.ERF, z, p);
363     }
364 
365     @Test
366     @Order(1010)
367     void testErfRMS() {
368         assertRms(TestCase.ERF);
369     }
370 
371     @ParameterizedTest
372     @Order(1)
373     @CsvFileSource(resources = "erf_data.csv")
374     void testErfc(double z, double p, BigDecimal q) {
375         assertErf(TestCase.ERFC, z, q);
376     }
377 
378     @Test
379     @Order(1020)
380     void testErfcRMS() {
381         assertRms(TestCase.ERFC);
382     }
383 
384     @ParameterizedTest
385     @Order(1)
386     @CsvFileSource(resources = "erf_large_data.csv")
387     void testErfLarge(double z, BigDecimal p, double q) {
388         assertErf(TestCase.ERF_LARGE, z, p);
389     }
390 
391     @Test
392     @Order(1030)
393     void testErfLargeRMS() {
394         assertRms(TestCase.ERF_LARGE);
395     }
396 
397     @ParameterizedTest
398     @Order(1)
399     @CsvFileSource(resources = "erf_large_data.csv")
400     void testErfcLarge(double z, double p, BigDecimal q) {
401         assertErf(TestCase.ERFC_LARGE, z, q);
402     }
403 
404     @Test
405     @Order(1040)
406     void testErfcLargeRMS() {
407         assertRms(TestCase.ERFC_LARGE);
408     }
409 
410     @ParameterizedTest
411     @Order(1)
412     @CsvFileSource(resources = "erf_small_data.csv")
413     void testErfSmall(double z, BigDecimal p, double q) {
414         assertErf(TestCase.ERF_SMALL, z, p);
415     }
416 
417     @Test
418     @Order(1050)
419     void testErfSmallRMS() {
420         assertRms(TestCase.ERF_SMALL);
421     }
422 
423     @ParameterizedTest
424     @Order(1)
425     @CsvFileSource(resources = "erf_small_data.csv")
426     void testErfcSmall(double z, double p, BigDecimal q) {
427         assertErf(TestCase.ERFC_SMALL, z, q);
428     }
429 
430     @Test
431     @Order(1060)
432     void testErfcSmallRMS() {
433         assertRms(TestCase.ERFC_SMALL);
434     }
435 
436     /**
437      * This tests the erf function as the result approaches 1.0. The Boost threshold
438      * for large z is 5.8f. This is too low and results in 2 ulp errors when z is
439      * just above 5.8 and the result is 0.9999999999999998.
440      *
441      * @param z Value to test
442      * @param p Expected p
443      */
444     @ParameterizedTest
445     @CsvFileSource(resources = "erf_close_to_1_data.csv")
446     void testErfCloseTo1(double z, double p) {
447         Assertions.assertTrue(5.8 < z, () -> "z not above Boost threshold: " + z);
448         Assertions.assertTrue(z < 5.95, () -> "z not close to Boost threhsold: " + z);
449         Assertions.assertTrue(p <= 1.0, () -> "p not <= 1: " + p);
450         Assertions.assertEquals(1.0, p, 0x1.0p-52, "Value not with 2 ulp of 1.0");
451         Assertions.assertEquals(p, BoostErf.erf(z));
452     }
453 
454     @ParameterizedTest
455     @CsvSource({
456         "0, 0",
457         // Odd function
458         "-0.0, -0.0",
459         "1, Infinity",
460         "-1, -Infinity",
461         "NaN, NaN",
462         // Domain errors do not throw
463         "-1.1, NaN",
464         "1.1, NaN",
465     })
466     void testInverseErfEdgeCases(double p, double z) {
467         Assertions.assertEquals(z, BoostErf.erfInv(p));
468     }
469 
470     @ParameterizedTest
471     @CsvSource({
472         "1, 0",
473         "0, Infinity",
474         "2, -Infinity",
475         "NaN, NaN",
476         // Domain errors do not throw
477         "-0.1, NaN",
478         "2.1, NaN",
479     })
480     void testInverseErfcEdgeCases(double p, double z) {
481         Assertions.assertEquals(z, BoostErf.erfcInv(p));
482     }
483 
484     @ParameterizedTest
485     @Order(1)
486     @CsvFileSource(resources = "erf_inv_data.csv")
487     void testInverseErf(double p, BigDecimal z) {
488         assertErf(TestCase.ERF_INV, p, z);
489     }
490 
491     @Test
492     @Order(2010)
493     void testInverseErfRMS() {
494         assertRms(TestCase.ERF_INV);
495     }
496 
497     @ParameterizedTest
498     @Order(1)
499     @CsvFileSource(resources = "erfc_inv_data.csv")
500     void testInverseErfc(double q, BigDecimal z) {
501         assertErf(TestCase.ERFC_INV, q, z);
502     }
503 
504     @Test
505     @Order(2020)
506     void testInverseErfcRMS() {
507         assertRms(TestCase.ERFC_INV);
508     }
509 
510     @ParameterizedTest
511     @Order(1)
512     @CsvFileSource(resources = "erfc_inv_big_data.csv")
513     void testInverseErfcBig(double q, BigDecimal z) {
514         assertErf(TestCase.ERFC_INV_BIG, q, z);
515     }
516 
517     @Test
518     @Order(2030)
519     void testInverseErfcBigRMS() {
520         assertRms(TestCase.ERFC_INV_BIG);
521     }
522 
523     @ParameterizedTest
524     @Order(1)
525     @CsvFileSource(resources = "erf_inv_limit_data.csv")
526     void testInverseErfLimit(double p, BigDecimal z) {
527         assertErf(TestCase.ERF_INV_LIMIT, p, z);
528     }
529 
530     @Test
531     @Order(2040)
532     void testInverseErfLimitRMS() {
533         assertRms(TestCase.ERF_INV_LIMIT);
534     }
535 
536     @ParameterizedTest
537     @Order(1)
538     @CsvFileSource(resources = "erfc_inv_limit_data.csv")
539     void testInverseErfcLimit(double q, BigDecimal z) {
540         assertErf(TestCase.ERFC_INV_LIMIT, q, z);
541     }
542 
543     @Test
544     @Order(2050)
545     void testInverseErfcLimitRMS() {
546         assertRms(TestCase.ERFC_INV_LIMIT);
547     }
548 
549     /**
550      * Round-trip test of the inverse erf and then the erf to return to the original value.
551      */
552     @Test
553     void testErfRoundTrip() {
554         assertRoundTrip("erf(erfInv(x))",
555             x -> BoostErf.erf(BoostErf.erfInv(x)),
556             // Inverse Erf domain: [-1, 1]
557             -0.95, 1, 0.125,
558             2L, 0.99);
559     }
560 
561     /**
562      * Round-trip test of the inverse erfc and then the erfc to return to the original value.
563      */
564     @Test
565     void testErfcRoundTrip() {
566         assertRoundTrip("erfc(erfcInv(x))",
567             x -> BoostErf.erfc(BoostErf.erfcInv(x)),
568             // Inverse Erfc domain: [0, 2]
569             0.125, 2, 0.125,
570             2L, 0.99);
571     }
572 
573     /**
574      * Test a round-trip function (foward and reverse) returns to the original value.
575      *
576      * @param name Test name
577      * @param fun Round-trip function
578      * @param low Low bound to test
579      * @param high Upper bound to test
580      * @param increment Increment between bounds
581      * @param tolerance Maximum ULP tolerance
582      * @param rmsUlp Maximum RMS ULP
583      */
584     private static void assertRoundTrip(String name,
585             DoubleUnaryOperator fun,
586             double low, double high, double increment,
587             long tolerance, double rmsUlp) {
588         final TestUtils.ErrorStatistics data = new TestUtils.ErrorStatistics();
589         for (double p = low; p <= high; p += increment) {
590             final double pp = fun.applyAsDouble(p);
591             TestUtils.assertEquals(p, pp, tolerance, ulp -> data.add(ulp), () -> name);
592         }
593         assertRms(name, data, rmsUlp);
594     }
595 
596     @ParameterizedTest
597     @CsvSource({
598         "NaN, NaN",
599         "0, 1",
600         "-0.0, 1",
601         "1e-100, 1",
602         // expected 1.0000000000000002
603         "-2.220446049250313E-16, 1.0000000000000002505505",
604         // expected 1.0
605         "-1.1102230246251565E-16, 1.000000000000000125275",
606         "1.734723475976807e-17, 0.99999999999999998042574169",
607         // expected 0.9999999999999999
608         "5.551115123125783e-17, 0.99999999999999993736237340916",
609         // max value
610         "1.7976931348623157e308, 3.13840873398544321279297017922274491e-309",
611         "Infinity, 0",
612         "-27, Infinity",
613         "-26.62873571375149, 1.7976931348622485388617592502115433e+308",
614         // This is infinite as a double: matlab erfcx = Inf
615         "-26.628735713751492, 1.79769313486258867776818776259144527e+308",
616     })
617     void testErfcxEdgeCases(double z, double expected) {
618         final double actual = BoostErf.erfcx(z);
619         // Workaround for NaN not having a valid ulp
620         if (Double.isFinite(expected)) {
621             Assertions.assertEquals(expected, actual, Math.ulp(expected));
622         } else {
623             Assertions.assertEquals(expected, actual);
624         }
625     }
626 
627     @ParameterizedTest
628     @CsvSource({
629         // matlab: fprintf("%.17g\n, erfcx(z))
630         "-24.356, 8.5293595881160216e+257",
631         "-12.34, 2.7132062210034015e+66",
632         "-6.89, 8.2775358436372447e+20",
633         "-1.11134, 6.4783098090861095",
634         "-0.67868, 2.6356650381821858",
635         "-0.1234, 1.156008270595601",
636         "0.1234, 0.87467990946395457",
637         "0.2836, 0.74601996202714793",
638         "0.7521364, 0.5061525663103037",
639         "1.678, 0.2947001506216585",
640         "2.67868, 0.19827490572168827",
641         "5.6788, 0.09787639472753934",
642         "10.67182, 0.052638121464397732",
643         "15.678, 0.035913308816213706",
644         "23.975, 0.023511995366729203",
645         "26.8989, 0.020959983993738648",
646         "27.1678, 0.020752808901245822",
647         "27.789, 0.020289502724156101",
648         "28.4567, 0.019814028635674531",
649         "33.67868, 0.016744753846685077",
650         "56.567, 0.0099722712078122132",
651         "101.101, 0.0055801820870247853",
652         "234.7, 0.0024038536962965006",
653         "678658.678, 8.3133039013043281e-07",
654     })
655     void testErfcxSpotTests(double z, double expected) {
656         final double actual = BoostErf.erfcx(z);
657         Assertions.assertEquals(expected, actual, Math.ulp(expected));
658     }
659 
660     @ParameterizedTest
661     @Order(1)
662     @CsvFileSource(resources = "erfcx_neg_medium_data.csv")
663     void testErfcxNegMedium(double z, BigDecimal expected) {
664         assertErf(TestCase.ERFCX_NEG_MEDIUM, z, expected);
665     }
666 
667     @Test
668     @Order(3010)
669     void testErfcxNegMediumRMS() {
670         assertRms(TestCase.ERFCX_NEG_MEDIUM);
671     }
672 
673     @ParameterizedTest
674     @Order(1)
675     @CsvFileSource(resources = "erfcx_neg_small_data.csv")
676     void testErfcxNegSmall(double z, BigDecimal expected) {
677         assertErf(TestCase.ERFCX_NEG_SMALL, z, expected);
678     }
679 
680     @Test
681     @Order(3020)
682     void testErfcxNegSmallRMS() {
683         assertRms(TestCase.ERFCX_NEG_SMALL);
684     }
685 
686     @ParameterizedTest
687     @Order(1)
688     @CsvFileSource(resources = "erfcx_small_data.csv")
689     void testErfcxSmall(double z, BigDecimal expected) {
690         assertErf(TestCase.ERFCX_SMALL, z, expected);
691     }
692 
693     @Test
694     @Order(3030)
695     void testErfcxSmallRMS() {
696         assertRms(TestCase.ERFCX_SMALL);
697     }
698 
699     @ParameterizedTest
700     @Order(1)
701     @CsvFileSource(resources = "erfcx_medium_data.csv")
702     void testErfcxMedium(double z, BigDecimal expected) {
703         assertErf(TestCase.ERFCX_MED, z, expected);
704     }
705 
706     @Test
707     @Order(3040)
708     void testErfcxMediumRMS() {
709         assertRms(TestCase.ERFCX_MED);
710     }
711 
712     @ParameterizedTest
713     @Order(1)
714     @CsvFileSource(resources = "erfcx_large_data.csv")
715     void testErfcxLarge(double z, BigDecimal expected) {
716         assertErf(TestCase.ERFCX_LARGE, z, expected);
717     }
718 
719     @Test
720     @Order(3050)
721     void testErfcxLargeRMS() {
722         assertRms(TestCase.ERFCX_LARGE);
723     }
724 
725     @ParameterizedTest
726     @Order(1)
727     @CsvFileSource(resources = "erfcx_huge_data.csv")
728     void testErfcxHuge(double z, BigDecimal expected) {
729         assertErf(TestCase.ERFCX_HUGE, z, expected);
730     }
731 
732     @Test
733     @Order(3060)
734     void testErfcxHugeRMS() {
735         assertRms(TestCase.ERFCX_HUGE);
736     }
737 
738     /**
739      * Assert the function using extended precision.
740      *
741      * @param tc Test case
742      * @param x Input value
743      * @param expected Expected value
744      */
745     private static void assertErf(TestCase tc, double x, BigDecimal expected) {
746         final double actual = tc.getFunction().applyAsDouble(x);
747         TestUtils.assertEquals(expected, actual, tc.getTolerance(), tc::addError,
748             () -> tc + " x=" + x);
749         if (tc.isOdd()) {
750             // Use a delta of 0.0 to allow -0.0 == 0.0
751             Assertions.assertEquals(actual, -tc.getFunction().applyAsDouble(-x), 0.0, "odd function: f(x) = -f(-x)");
752         }
753     }
754 
755     /**
756      * Assert the Root Mean Square (RMS) error of the function is below the allowed maximum.
757      *
758      * @param tc Test case
759      */
760     private static void assertRms(TestCase tc) {
761         final double rms = tc.getRMSError();
762         debugRms(tc.toString(), tc.getMaxAbsError(), rms);
763         Assertions.assertTrue(rms <= tc.getRmsTolerance(),
764             () -> String.format("%s RMS %s < %s", tc, rms, tc.getRmsTolerance()));
765     }
766 
767     /**
768      * Assert the Root Mean Square (RMS) error of the function is below the allowed maximum.
769      *
770      * @param name Test name
771      * @param data Test data
772      * @param rmsTolerance RMS tolerance
773      */
774     private static void assertRms(String name, TestUtils.ErrorStatistics data, double rmsTolerance) {
775         final double rms = data.getRMS();
776         debugRms(name, data.getMaxAbs(), rms);
777         Assertions.assertTrue(rms <= rmsTolerance,
778             () -> String.format("%s RMS %s < %s", name, rms, rmsTolerance));
779     }
780 
781     /**
782      * Output the maximum and RMS ulp for the named test.
783      * Used for reporting the errors and setting appropriate test tolerances.
784      * This is relevant across different JDK implementations where the java.util.Math
785      * functions used in BoostErf may compute to different accuracy.
786      *
787      * @param name Test name
788      * @param maxUlp Maximum ulp
789      * @param rmsUlp RMS ulp
790      */
791     private static void debugRms(String name, double maxUlp, double rmsUlp) {
792         // CHECKSTYLE: stop regexp
793         // Debug output of max and RMS error.
794         //System.out.printf("%-25s   max %10.6g   RMS %10.6g%n", name, maxUlp, rmsUlp);
795     }
796 }