131914882SAlex Richardson /*
231914882SAlex Richardson * Generic functions for ULP error estimation.
331914882SAlex Richardson *
4*f3087befSAndrew Turner * Copyright (c) 2019-2024, Arm Limited.
5072a4ba8SAndrew Turner * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception
631914882SAlex Richardson */
731914882SAlex Richardson
831914882SAlex Richardson /* For each different math function type,
931914882SAlex Richardson T(x) should add a different suffix to x.
1031914882SAlex Richardson RT(x) should add a return type specific suffix to x. */
1131914882SAlex Richardson
1231914882SAlex Richardson #ifdef NEW_RT
1331914882SAlex Richardson #undef NEW_RT
1431914882SAlex Richardson
1531914882SAlex Richardson # if USE_MPFR
RT(ulpscale_mpfr)1631914882SAlex Richardson static int RT(ulpscale_mpfr) (mpfr_t x, int t)
1731914882SAlex Richardson {
1831914882SAlex Richardson /* TODO: pow of 2 cases. */
1931914882SAlex Richardson if (mpfr_regular_p (x))
2031914882SAlex Richardson {
2131914882SAlex Richardson mpfr_exp_t e = mpfr_get_exp (x) - RT(prec);
2231914882SAlex Richardson if (e < RT(emin))
2331914882SAlex Richardson e = RT(emin) - 1;
2431914882SAlex Richardson if (e > RT(emax) - RT(prec))
2531914882SAlex Richardson e = RT(emax) - RT(prec);
2631914882SAlex Richardson return e;
2731914882SAlex Richardson }
2831914882SAlex Richardson if (mpfr_zero_p (x))
2931914882SAlex Richardson return RT(emin) - 1;
3031914882SAlex Richardson if (mpfr_inf_p (x))
3131914882SAlex Richardson return RT(emax) - RT(prec);
3231914882SAlex Richardson /* NaN. */
3331914882SAlex Richardson return 0;
3431914882SAlex Richardson }
3531914882SAlex Richardson # endif
3631914882SAlex Richardson
3731914882SAlex Richardson /* Difference between exact result and closest real number that
3831914882SAlex Richardson gets rounded to got, i.e. error before rounding, for a correctly
3931914882SAlex Richardson rounded result the difference is 0. */
RT(ulperr)405a02ffc3SAndrew Turner static double RT (ulperr) (RT (float) got, const struct RT (ret) * p, int r,
415a02ffc3SAndrew Turner int ignore_zero_sign)
4231914882SAlex Richardson {
4331914882SAlex Richardson RT(float) want = p->y;
4431914882SAlex Richardson RT(float) d;
4531914882SAlex Richardson double e;
4631914882SAlex Richardson
4731914882SAlex Richardson if (RT(asuint) (got) == RT(asuint) (want))
4831914882SAlex Richardson return 0.0;
495a02ffc3SAndrew Turner if (isnan (got) && isnan (want))
50*f3087befSAndrew Turner /* Ignore sign of NaN, and signalling-ness for MPFR. */
51*f3087befSAndrew Turner # if USE_MPFR
52*f3087befSAndrew Turner return 0;
53*f3087befSAndrew Turner # else
545a02ffc3SAndrew Turner return RT (issignaling) (got) == RT (issignaling) (want) ? 0 : INFINITY;
55*f3087befSAndrew Turner # endif
5631914882SAlex Richardson if (signbit (got) != signbit (want))
575a02ffc3SAndrew Turner {
585a02ffc3SAndrew Turner /* Fall through to ULP calculation if ignoring sign of zero and at
595a02ffc3SAndrew Turner exactly one of want and got is non-zero. */
605a02ffc3SAndrew Turner if (ignore_zero_sign && want == got)
615a02ffc3SAndrew Turner return 0.0;
625a02ffc3SAndrew Turner if (!ignore_zero_sign || (want != 0 && got != 0))
6331914882SAlex Richardson return INFINITY;
645a02ffc3SAndrew Turner }
6531914882SAlex Richardson if (!isfinite (want) || !isfinite (got))
6631914882SAlex Richardson {
6731914882SAlex Richardson if (isnan (got) != isnan (want))
6831914882SAlex Richardson return INFINITY;
6931914882SAlex Richardson if (isnan (want))
7031914882SAlex Richardson return 0;
7131914882SAlex Richardson if (isinf (got))
7231914882SAlex Richardson {
7331914882SAlex Richardson got = RT(copysign) (RT(halfinf), got);
7431914882SAlex Richardson want *= 0.5f;
7531914882SAlex Richardson }
7631914882SAlex Richardson if (isinf (want))
7731914882SAlex Richardson {
7831914882SAlex Richardson want = RT(copysign) (RT(halfinf), want);
7931914882SAlex Richardson got *= 0.5f;
8031914882SAlex Richardson }
8131914882SAlex Richardson }
8231914882SAlex Richardson if (r == FE_TONEAREST)
8331914882SAlex Richardson {
8431914882SAlex Richardson // TODO: incorrect when got vs want cross a powof2 boundary
8531914882SAlex Richardson /* error = got > want
8631914882SAlex Richardson ? got - want - tail ulp - 0.5 ulp
87*f3087befSAndrew Turner : got - want - tail ulp + 0.5 ulp. */
8831914882SAlex Richardson d = got - want;
8931914882SAlex Richardson e = d > 0 ? -p->tail - 0.5 : -p->tail + 0.5;
9031914882SAlex Richardson }
9131914882SAlex Richardson else
9231914882SAlex Richardson {
9331914882SAlex Richardson if ((r == FE_DOWNWARD && got < want) || (r == FE_UPWARD && got > want)
9431914882SAlex Richardson || (r == FE_TOWARDZERO && fabs (got) < fabs (want)))
9531914882SAlex Richardson got = RT(nextafter) (got, want);
9631914882SAlex Richardson d = got - want;
9731914882SAlex Richardson e = -p->tail;
9831914882SAlex Richardson }
9931914882SAlex Richardson return RT(scalbn) (d, -p->ulpexp) + e;
10031914882SAlex Richardson }
10131914882SAlex Richardson
RT(isok)10231914882SAlex Richardson static int RT(isok) (RT(float) ygot, int exgot, RT(float) ywant, int exwant,
10331914882SAlex Richardson int exmay)
10431914882SAlex Richardson {
10531914882SAlex Richardson return RT(asuint) (ygot) == RT(asuint) (ywant)
10631914882SAlex Richardson && ((exgot ^ exwant) & ~exmay) == 0;
10731914882SAlex Richardson }
10831914882SAlex Richardson
RT(isok_nofenv)10931914882SAlex Richardson static int RT(isok_nofenv) (RT(float) ygot, RT(float) ywant)
11031914882SAlex Richardson {
11131914882SAlex Richardson return RT(asuint) (ygot) == RT(asuint) (ywant);
11231914882SAlex Richardson }
11331914882SAlex Richardson #endif
11431914882SAlex Richardson
T(call_fenv)115*f3087befSAndrew Turner static inline void T (call_fenv) (const struct fun *f, struct T (args) a,
116*f3087befSAndrew Turner int r, RT (float) * y, int *ex,
117*f3087befSAndrew Turner const struct conf *conf)
11831914882SAlex Richardson {
11931914882SAlex Richardson if (r != FE_TONEAREST)
12031914882SAlex Richardson fesetround (r);
12131914882SAlex Richardson feclearexcept (FE_ALL_EXCEPT);
122*f3087befSAndrew Turner *y = T (call) (f, a, conf);
12331914882SAlex Richardson *ex = fetestexcept (FE_ALL_EXCEPT);
12431914882SAlex Richardson if (r != FE_TONEAREST)
12531914882SAlex Richardson fesetround (FE_TONEAREST);
12631914882SAlex Richardson }
12731914882SAlex Richardson
T(call_nofenv)12831914882SAlex Richardson static inline void T (call_nofenv) (const struct fun *f, struct T (args) a,
129*f3087befSAndrew Turner int r, RT (float) * y, int *ex,
130*f3087befSAndrew Turner const struct conf *conf)
13131914882SAlex Richardson {
1325a02ffc3SAndrew Turner if (r != FE_TONEAREST)
1335a02ffc3SAndrew Turner fesetround (r);
134*f3087befSAndrew Turner *y = T (call) (f, a, conf);
13531914882SAlex Richardson *ex = 0;
1365a02ffc3SAndrew Turner if (r != FE_TONEAREST)
1375a02ffc3SAndrew Turner fesetround (FE_TONEAREST);
13831914882SAlex Richardson }
13931914882SAlex Richardson
T(call_long_fenv)14031914882SAlex Richardson static inline int T (call_long_fenv) (const struct fun *f, struct T (args) a,
14131914882SAlex Richardson int r, struct RT (ret) * p,
14231914882SAlex Richardson RT (float) ygot, int exgot)
14331914882SAlex Richardson {
14431914882SAlex Richardson if (r != FE_TONEAREST)
14531914882SAlex Richardson fesetround (r);
14631914882SAlex Richardson feclearexcept (FE_ALL_EXCEPT);
14731914882SAlex Richardson volatile struct T(args) va = a; // TODO: barrier
14831914882SAlex Richardson a = va;
14931914882SAlex Richardson RT(double) yl = T(call_long) (f, a);
15031914882SAlex Richardson p->y = (RT(float)) yl;
15131914882SAlex Richardson volatile RT(float) vy = p->y; // TODO: barrier
15231914882SAlex Richardson (void) vy;
15331914882SAlex Richardson p->ex = fetestexcept (FE_ALL_EXCEPT);
15431914882SAlex Richardson if (r != FE_TONEAREST)
15531914882SAlex Richardson fesetround (FE_TONEAREST);
15631914882SAlex Richardson p->ex_may = FE_INEXACT;
15731914882SAlex Richardson if (RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may))
15831914882SAlex Richardson return 1;
15931914882SAlex Richardson p->ulpexp = RT(ulpscale) (p->y);
16031914882SAlex Richardson if (isinf (p->y))
16131914882SAlex Richardson p->tail = RT(lscalbn) (yl - (RT(double)) 2 * RT(halfinf), -p->ulpexp);
16231914882SAlex Richardson else
16331914882SAlex Richardson p->tail = RT(lscalbn) (yl - p->y, -p->ulpexp);
16431914882SAlex Richardson if (RT(fabs) (p->y) < RT(min_normal))
16531914882SAlex Richardson {
16631914882SAlex Richardson /* TODO: subnormal result is treated as undeflow even if it's
16731914882SAlex Richardson exact since call_long may not raise inexact correctly. */
16831914882SAlex Richardson if (p->y != 0 || (p->ex & FE_INEXACT))
16931914882SAlex Richardson p->ex |= FE_UNDERFLOW | FE_INEXACT;
17031914882SAlex Richardson }
17131914882SAlex Richardson return 0;
17231914882SAlex Richardson }
T(call_long_nofenv)17331914882SAlex Richardson static inline int T(call_long_nofenv) (const struct fun *f, struct T(args) a,
17431914882SAlex Richardson int r, struct RT(ret) * p,
17531914882SAlex Richardson RT(float) ygot, int exgot)
17631914882SAlex Richardson {
1775a02ffc3SAndrew Turner if (r != FE_TONEAREST)
1785a02ffc3SAndrew Turner fesetround (r);
17931914882SAlex Richardson RT(double) yl = T(call_long) (f, a);
18031914882SAlex Richardson p->y = (RT(float)) yl;
1815a02ffc3SAndrew Turner if (r != FE_TONEAREST)
1825a02ffc3SAndrew Turner fesetround (FE_TONEAREST);
18331914882SAlex Richardson if (RT(isok_nofenv) (ygot, p->y))
18431914882SAlex Richardson return 1;
18531914882SAlex Richardson p->ulpexp = RT(ulpscale) (p->y);
18631914882SAlex Richardson if (isinf (p->y))
18731914882SAlex Richardson p->tail = RT(lscalbn) (yl - (RT(double)) 2 * RT(halfinf), -p->ulpexp);
18831914882SAlex Richardson else
18931914882SAlex Richardson p->tail = RT(lscalbn) (yl - p->y, -p->ulpexp);
19031914882SAlex Richardson return 0;
19131914882SAlex Richardson }
19231914882SAlex Richardson
19331914882SAlex Richardson /* There are nan input args and all quiet. */
T(qnanpropagation)19431914882SAlex Richardson static inline int T(qnanpropagation) (struct T(args) a)
19531914882SAlex Richardson {
19631914882SAlex Richardson return T(reduce) (a, isnan, ||) && !T(reduce) (a, RT(issignaling), ||);
19731914882SAlex Richardson }
T(sum)19831914882SAlex Richardson static inline RT(float) T(sum) (struct T(args) a)
19931914882SAlex Richardson {
20031914882SAlex Richardson return T(reduce) (a, , +);
20131914882SAlex Richardson }
20231914882SAlex Richardson
20331914882SAlex Richardson /* returns 1 if the got result is ok. */
T(call_mpfr_fix)20431914882SAlex Richardson static inline int T(call_mpfr_fix) (const struct fun *f, struct T(args) a,
20531914882SAlex Richardson int r_fenv, struct RT(ret) * p,
20631914882SAlex Richardson RT(float) ygot, int exgot)
20731914882SAlex Richardson {
20831914882SAlex Richardson #if USE_MPFR
20931914882SAlex Richardson int t, t2;
21031914882SAlex Richardson mpfr_rnd_t r = rmap (r_fenv);
21131914882SAlex Richardson MPFR_DECL_INIT(my, RT(prec_mpfr));
21231914882SAlex Richardson MPFR_DECL_INIT(mr, RT(prec));
21331914882SAlex Richardson MPFR_DECL_INIT(me, RT(prec_mpfr));
21431914882SAlex Richardson mpfr_clear_flags ();
21531914882SAlex Richardson t = T(call_mpfr) (my, f, a, r);
21631914882SAlex Richardson /* Double rounding. */
21731914882SAlex Richardson t2 = mpfr_set (mr, my, r);
21831914882SAlex Richardson if (t2)
21931914882SAlex Richardson t = t2;
22031914882SAlex Richardson mpfr_set_emin (RT(emin));
22131914882SAlex Richardson mpfr_set_emax (RT(emax));
22231914882SAlex Richardson t = mpfr_check_range (mr, t, r);
22331914882SAlex Richardson t = mpfr_subnormalize (mr, t, r);
22431914882SAlex Richardson mpfr_set_emax (MPFR_EMAX_DEFAULT);
22531914882SAlex Richardson mpfr_set_emin (MPFR_EMIN_DEFAULT);
22631914882SAlex Richardson p->y = mpfr_get_d (mr, r);
22731914882SAlex Richardson p->ex = t ? FE_INEXACT : 0;
22831914882SAlex Richardson p->ex_may = FE_INEXACT;
22931914882SAlex Richardson if (mpfr_underflow_p () && (p->ex & FE_INEXACT))
23031914882SAlex Richardson /* TODO: handle before and after rounding uflow cases. */
23131914882SAlex Richardson p->ex |= FE_UNDERFLOW;
23231914882SAlex Richardson if (mpfr_overflow_p ())
23331914882SAlex Richardson p->ex |= FE_OVERFLOW | FE_INEXACT;
23431914882SAlex Richardson if (mpfr_divby0_p ())
23531914882SAlex Richardson p->ex |= FE_DIVBYZERO;
23631914882SAlex Richardson //if (mpfr_erangeflag_p ())
23731914882SAlex Richardson // p->ex |= FE_INVALID;
23831914882SAlex Richardson if (!mpfr_nanflag_p () && RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may))
23931914882SAlex Richardson return 1;
24031914882SAlex Richardson if (mpfr_nanflag_p () && !T(qnanpropagation) (a))
24131914882SAlex Richardson p->ex |= FE_INVALID;
24231914882SAlex Richardson p->ulpexp = RT(ulpscale_mpfr) (my, t);
24331914882SAlex Richardson if (!isfinite (p->y))
24431914882SAlex Richardson {
24531914882SAlex Richardson p->tail = 0;
24631914882SAlex Richardson if (isnan (p->y))
24731914882SAlex Richardson {
24831914882SAlex Richardson /* If an input was nan keep its sign. */
24931914882SAlex Richardson p->y = T(sum) (a);
25031914882SAlex Richardson if (!isnan (p->y))
25131914882SAlex Richardson p->y = (p->y - p->y) / (p->y - p->y);
25231914882SAlex Richardson return RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may);
25331914882SAlex Richardson }
25431914882SAlex Richardson mpfr_set_si_2exp (mr, signbit (p->y) ? -1 : 1, 1024, MPFR_RNDN);
25531914882SAlex Richardson if (mpfr_cmpabs (my, mr) >= 0)
25631914882SAlex Richardson return RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may);
25731914882SAlex Richardson }
25831914882SAlex Richardson mpfr_sub (me, my, mr, MPFR_RNDN);
25931914882SAlex Richardson mpfr_mul_2si (me, me, -p->ulpexp, MPFR_RNDN);
26031914882SAlex Richardson p->tail = mpfr_get_d (me, MPFR_RNDN);
26131914882SAlex Richardson return 0;
26231914882SAlex Richardson #else
26331914882SAlex Richardson abort ();
26431914882SAlex Richardson #endif
26531914882SAlex Richardson }
26631914882SAlex Richardson
T(cmp)26731914882SAlex Richardson static int T(cmp) (const struct fun *f, struct gen *gen,
26831914882SAlex Richardson const struct conf *conf)
26931914882SAlex Richardson {
27031914882SAlex Richardson double maxerr = 0;
27131914882SAlex Richardson uint64_t cnt = 0;
27231914882SAlex Richardson uint64_t cnt1 = 0;
27331914882SAlex Richardson uint64_t cnt2 = 0;
27431914882SAlex Richardson uint64_t cntfail = 0;
27531914882SAlex Richardson int r = conf->r;
27631914882SAlex Richardson int use_mpfr = conf->mpfr;
27731914882SAlex Richardson int fenv = conf->fenv;
278*f3087befSAndrew Turner
27931914882SAlex Richardson for (;;)
28031914882SAlex Richardson {
28131914882SAlex Richardson struct RT(ret) want;
28231914882SAlex Richardson struct T(args) a = T(next) (gen);
28331914882SAlex Richardson int exgot;
28431914882SAlex Richardson int exgot2;
28531914882SAlex Richardson RT(float) ygot;
28631914882SAlex Richardson RT(float) ygot2;
28731914882SAlex Richardson int fail = 0;
28831914882SAlex Richardson if (fenv)
289*f3087befSAndrew Turner T (call_fenv) (f, a, r, &ygot, &exgot, conf);
29031914882SAlex Richardson else
291*f3087befSAndrew Turner T (call_nofenv) (f, a, r, &ygot, &exgot, conf);
29231914882SAlex Richardson if (f->twice) {
29331914882SAlex Richardson secondcall = 1;
29431914882SAlex Richardson if (fenv)
295*f3087befSAndrew Turner T (call_fenv) (f, a, r, &ygot2, &exgot2, conf);
29631914882SAlex Richardson else
297*f3087befSAndrew Turner T (call_nofenv) (f, a, r, &ygot2, &exgot2, conf);
29831914882SAlex Richardson secondcall = 0;
29931914882SAlex Richardson if (RT(asuint) (ygot) != RT(asuint) (ygot2))
30031914882SAlex Richardson {
30131914882SAlex Richardson fail = 1;
30231914882SAlex Richardson cntfail++;
30331914882SAlex Richardson T(printcall) (f, a);
30431914882SAlex Richardson printf (" got %a then %a for same input\n", ygot, ygot2);
30531914882SAlex Richardson }
30631914882SAlex Richardson }
30731914882SAlex Richardson cnt++;
30831914882SAlex Richardson int ok = use_mpfr
30931914882SAlex Richardson ? T(call_mpfr_fix) (f, a, r, &want, ygot, exgot)
31031914882SAlex Richardson : (fenv ? T(call_long_fenv) (f, a, r, &want, ygot, exgot)
31131914882SAlex Richardson : T(call_long_nofenv) (f, a, r, &want, ygot, exgot));
31231914882SAlex Richardson if (!ok)
31331914882SAlex Richardson {
31431914882SAlex Richardson int print = 0;
3155a02ffc3SAndrew Turner double err = RT (ulperr) (ygot, &want, r, conf->ignore_zero_sign);
31631914882SAlex Richardson double abserr = fabs (err);
31731914882SAlex Richardson // TODO: count errors below accuracy limit.
31831914882SAlex Richardson if (abserr > 0)
31931914882SAlex Richardson cnt1++;
32031914882SAlex Richardson if (abserr > 1)
32131914882SAlex Richardson cnt2++;
32231914882SAlex Richardson if (abserr > conf->errlim)
32331914882SAlex Richardson {
32431914882SAlex Richardson print = 1;
32531914882SAlex Richardson if (!fail)
32631914882SAlex Richardson {
32731914882SAlex Richardson fail = 1;
32831914882SAlex Richardson cntfail++;
32931914882SAlex Richardson }
33031914882SAlex Richardson }
33131914882SAlex Richardson if (abserr > maxerr)
33231914882SAlex Richardson {
33331914882SAlex Richardson maxerr = abserr;
33431914882SAlex Richardson if (!conf->quiet && abserr > conf->softlim)
33531914882SAlex Richardson print = 1;
33631914882SAlex Richardson }
33731914882SAlex Richardson if (print)
33831914882SAlex Richardson {
33931914882SAlex Richardson T(printcall) (f, a);
34031914882SAlex Richardson // TODO: inf ulp handling
34131914882SAlex Richardson printf (" got %a want %a %+g ulp err %g\n", ygot, want.y,
34231914882SAlex Richardson want.tail, err);
34331914882SAlex Richardson }
34431914882SAlex Richardson int diff = fenv ? exgot ^ want.ex : 0;
34531914882SAlex Richardson if (fenv && (diff & ~want.ex_may))
34631914882SAlex Richardson {
34731914882SAlex Richardson if (!fail)
34831914882SAlex Richardson {
34931914882SAlex Richardson fail = 1;
35031914882SAlex Richardson cntfail++;
35131914882SAlex Richardson }
35231914882SAlex Richardson T(printcall) (f, a);
35331914882SAlex Richardson printf (" is %a %+g ulp, got except 0x%0x", want.y, want.tail,
35431914882SAlex Richardson exgot);
35531914882SAlex Richardson if (diff & exgot)
35631914882SAlex Richardson printf (" wrongly set: 0x%x", diff & exgot);
35731914882SAlex Richardson if (diff & ~exgot)
35831914882SAlex Richardson printf (" wrongly clear: 0x%x", diff & ~exgot);
35931914882SAlex Richardson putchar ('\n');
36031914882SAlex Richardson }
36131914882SAlex Richardson }
36231914882SAlex Richardson if (cnt >= conf->n)
36331914882SAlex Richardson break;
36431914882SAlex Richardson if (!conf->quiet && cnt % 0x100000 == 0)
36531914882SAlex Richardson printf ("progress: %6.3f%% cnt %llu cnt1 %llu cnt2 %llu cntfail %llu "
36631914882SAlex Richardson "maxerr %g\n",
36731914882SAlex Richardson 100.0 * cnt / conf->n, (unsigned long long) cnt,
36831914882SAlex Richardson (unsigned long long) cnt1, (unsigned long long) cnt2,
36931914882SAlex Richardson (unsigned long long) cntfail, maxerr);
37031914882SAlex Richardson }
37131914882SAlex Richardson double cc = cnt;
37231914882SAlex Richardson if (cntfail)
37331914882SAlex Richardson printf ("FAIL ");
37431914882SAlex Richardson else
37531914882SAlex Richardson printf ("PASS ");
37631914882SAlex Richardson T(printgen) (f, gen);
37731914882SAlex Richardson printf (" round %c errlim %g maxerr %g %s cnt %llu cnt1 %llu %g%% cnt2 %llu "
37831914882SAlex Richardson "%g%% cntfail %llu %g%%\n",
37931914882SAlex Richardson conf->rc, conf->errlim,
38031914882SAlex Richardson maxerr, conf->r == FE_TONEAREST ? "+0.5" : "+1.0",
38131914882SAlex Richardson (unsigned long long) cnt,
38231914882SAlex Richardson (unsigned long long) cnt1, 100.0 * cnt1 / cc,
38331914882SAlex Richardson (unsigned long long) cnt2, 100.0 * cnt2 / cc,
38431914882SAlex Richardson (unsigned long long) cntfail, 100.0 * cntfail / cc);
38531914882SAlex Richardson return !!cntfail;
38631914882SAlex Richardson }
387