1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
32
33
34
35
36 @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
37 class BoostErfTest {
38
39
40
41
42
43
44
45
46
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
56 private final DoubleUnaryOperator fun;
57
58
59 private final boolean odd;
60
61
62
63
64
65 TestFunction(DoubleUnaryOperator fun, boolean odd) {
66 this.fun = fun;
67 this.odd = odd;
68 }
69
70
71
72
73 DoubleUnaryOperator getFunction() {
74 return fun;
75 }
76
77
78
79
80 boolean isOdd() {
81 return odd;
82 }
83 }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114 private enum TestCase {
115
116 ERF(TestFunction.ERF, 1.3, 0.2),
117
118 ERFC(TestFunction.ERFC, 2.2, 0.5),
119
120 ERF_LARGE(TestFunction.ERF, 0, 0.0),
121
122 ERFC_LARGE(TestFunction.ERFC, 2.0, 0.7),
123
124 ERF_SMALL(TestFunction.ERF, 1.2, 0.25),
125
126 ERFC_SMALL(TestFunction.ERFC, 0, 0.0),
127
128 ERF_INV(TestFunction.ERF_INV, 1.8, 0.65),
129
130 ERFC_INV(TestFunction.ERFC_INV, 1.8, 0.65),
131
132 ERFC_INV_BIG(TestFunction.ERFC_INV, 1.8, 0.5),
133
134 ERF_INV_LIMIT(TestFunction.ERF_INV, 1.4, 0.5),
135
136 ERFC_INV_LIMIT(TestFunction.ERFC_INV, 1.2, 0.5),
137
138 ERFCX_NEG_MEDIUM(TestFunction.ERFCX, 1.7, 0.55),
139
140 ERFCX_NEG_SMALL(TestFunction.ERFCX, 0.7, 0.061),
141
142 ERFCX_SMALL(TestFunction.ERFCX, 0.6, 0.073),
143
144 ERFCX_MED(TestFunction.ERFCX, 1.2, 0.5),
145
146 ERFCX_LARGE(TestFunction.ERFCX, 1.1, 0.45),
147
148 ERFCX_HUGE(TestFunction.ERFCX, 1.25, 0.45);
149
150
151 private final TestUtils.ErrorStatistics stats = new TestUtils.ErrorStatistics();
152
153
154 private final TestFunction fun;
155
156
157 private final double maxUlp;
158
159
160 private final double rmsUlp;
161
162
163
164
165
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
175
176 DoubleUnaryOperator getFunction() {
177 return fun.getFunction();
178 }
179
180
181
182
183 boolean isOdd() {
184 return fun.isOdd();
185 }
186
187
188
189
190 void addError(double ulp) {
191 stats.add(ulp);
192 }
193
194
195
196
197 double getRMSError() {
198 return stats.getRMS();
199 }
200
201
202
203
204 double getMaxAbsError() {
205 return stats.getMaxAbs();
206 }
207
208
209
210
211 double getTolerance() {
212 return maxUlp;
213 }
214
215
216
217
218 double getRmsTolerance() {
219 return rmsUlp;
220 }
221 }
222
223 @ParameterizedTest
224 @CsvSource({
225 "0, 1",
226
227
228
229
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
251
252
253
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
264 "27, 3.98728526204259656354686104733435016e+316",
265 "27.45162436, 1.79769313486244732925408679719502907e+308",
266
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
280
281
282
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
305 "27.297128403953796, 2.47032822920651974943004172315065178e-324",
306
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
317
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
334 "-0.0, -0.0",
335 "NaN, NaN",
336
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
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
438
439
440
441
442
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
458 "-0.0, -0.0",
459 "1, Infinity",
460 "-1, -Infinity",
461 "NaN, NaN",
462
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
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
551
552 @Test
553 void testErfRoundTrip() {
554 assertRoundTrip("erf(erfInv(x))",
555 x -> BoostErf.erf(BoostErf.erfInv(x)),
556
557 -0.95, 1, 0.125,
558 2L, 0.99);
559 }
560
561
562
563
564 @Test
565 void testErfcRoundTrip() {
566 assertRoundTrip("erfc(erfcInv(x))",
567 x -> BoostErf.erfc(BoostErf.erfcInv(x)),
568
569 0.125, 2, 0.125,
570 2L, 0.99);
571 }
572
573
574
575
576
577
578
579
580
581
582
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
603 "-2.220446049250313E-16, 1.0000000000000002505505",
604
605 "-1.1102230246251565E-16, 1.000000000000000125275",
606 "1.734723475976807e-17, 0.99999999999999998042574169",
607
608 "5.551115123125783e-17, 0.99999999999999993736237340916",
609
610 "1.7976931348623157e308, 3.13840873398544321279297017922274491e-309",
611 "Infinity, 0",
612 "-27, Infinity",
613 "-26.62873571375149, 1.7976931348622485388617592502115433e+308",
614
615 "-26.628735713751492, 1.79769313486258867776818776259144527e+308",
616 })
617 void testErfcxEdgeCases(double z, double expected) {
618 final double actual = BoostErf.erfcx(z);
619
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
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
740
741
742
743
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
751 Assertions.assertEquals(actual, -tc.getFunction().applyAsDouble(-x), 0.0, "odd function: f(x) = -f(-x)");
752 }
753 }
754
755
756
757
758
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
769
770
771
772
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
783
784
785
786
787
788
789
790
791 private static void debugRms(String name, double maxUlp, double rmsUlp) {
792
793
794
795 }
796 }