1*412f47f9SXin Li /*
2*412f47f9SXin Li * Generic functions for ULP error estimation.
3*412f47f9SXin Li *
4*412f47f9SXin Li * Copyright (c) 2019-2024, Arm Limited.
5*412f47f9SXin Li * SPDX-License-Identifier: MIT OR Apache-2.0 WITH LLVM-exception
6*412f47f9SXin Li */
7*412f47f9SXin Li
8*412f47f9SXin Li /* For each different math function type,
9*412f47f9SXin Li T(x) should add a different suffix to x.
10*412f47f9SXin Li RT(x) should add a return type specific suffix to x. */
11*412f47f9SXin Li
12*412f47f9SXin Li #ifdef NEW_RT
13*412f47f9SXin Li #undef NEW_RT
14*412f47f9SXin Li
15*412f47f9SXin Li # if USE_MPFR
RT(ulpscale_mpfr)16*412f47f9SXin Li static int RT(ulpscale_mpfr) (mpfr_t x, int t)
17*412f47f9SXin Li {
18*412f47f9SXin Li /* TODO: pow of 2 cases. */
19*412f47f9SXin Li if (mpfr_regular_p (x))
20*412f47f9SXin Li {
21*412f47f9SXin Li mpfr_exp_t e = mpfr_get_exp (x) - RT(prec);
22*412f47f9SXin Li if (e < RT(emin))
23*412f47f9SXin Li e = RT(emin) - 1;
24*412f47f9SXin Li if (e > RT(emax) - RT(prec))
25*412f47f9SXin Li e = RT(emax) - RT(prec);
26*412f47f9SXin Li return e;
27*412f47f9SXin Li }
28*412f47f9SXin Li if (mpfr_zero_p (x))
29*412f47f9SXin Li return RT(emin) - 1;
30*412f47f9SXin Li if (mpfr_inf_p (x))
31*412f47f9SXin Li return RT(emax) - RT(prec);
32*412f47f9SXin Li /* NaN. */
33*412f47f9SXin Li return 0;
34*412f47f9SXin Li }
35*412f47f9SXin Li # endif
36*412f47f9SXin Li
37*412f47f9SXin Li /* Difference between exact result and closest real number that
38*412f47f9SXin Li gets rounded to got, i.e. error before rounding, for a correctly
39*412f47f9SXin Li rounded result the difference is 0. */
RT(ulperr)40*412f47f9SXin Li static double RT (ulperr) (RT (float) got, const struct RT (ret) * p, int r,
41*412f47f9SXin Li int ignore_zero_sign)
42*412f47f9SXin Li {
43*412f47f9SXin Li RT(float) want = p->y;
44*412f47f9SXin Li RT(float) d;
45*412f47f9SXin Li double e;
46*412f47f9SXin Li
47*412f47f9SXin Li if (RT(asuint) (got) == RT(asuint) (want))
48*412f47f9SXin Li return 0.0;
49*412f47f9SXin Li if (isnan (got) && isnan (want))
50*412f47f9SXin Li /* Ignore sign of NaN. */
51*412f47f9SXin Li return RT (issignaling) (got) == RT (issignaling) (want) ? 0 : INFINITY;
52*412f47f9SXin Li if (signbit (got) != signbit (want))
53*412f47f9SXin Li {
54*412f47f9SXin Li /* Fall through to ULP calculation if ignoring sign of zero and at
55*412f47f9SXin Li exactly one of want and got is non-zero. */
56*412f47f9SXin Li if (ignore_zero_sign && want == got)
57*412f47f9SXin Li return 0.0;
58*412f47f9SXin Li if (!ignore_zero_sign || (want != 0 && got != 0))
59*412f47f9SXin Li return INFINITY;
60*412f47f9SXin Li }
61*412f47f9SXin Li if (!isfinite (want) || !isfinite (got))
62*412f47f9SXin Li {
63*412f47f9SXin Li if (isnan (got) != isnan (want))
64*412f47f9SXin Li return INFINITY;
65*412f47f9SXin Li if (isnan (want))
66*412f47f9SXin Li return 0;
67*412f47f9SXin Li if (isinf (got))
68*412f47f9SXin Li {
69*412f47f9SXin Li got = RT(copysign) (RT(halfinf), got);
70*412f47f9SXin Li want *= 0.5f;
71*412f47f9SXin Li }
72*412f47f9SXin Li if (isinf (want))
73*412f47f9SXin Li {
74*412f47f9SXin Li want = RT(copysign) (RT(halfinf), want);
75*412f47f9SXin Li got *= 0.5f;
76*412f47f9SXin Li }
77*412f47f9SXin Li }
78*412f47f9SXin Li if (r == FE_TONEAREST)
79*412f47f9SXin Li {
80*412f47f9SXin Li // TODO: incorrect when got vs want cross a powof2 boundary
81*412f47f9SXin Li /* error = got > want
82*412f47f9SXin Li ? got - want - tail ulp - 0.5 ulp
83*412f47f9SXin Li : got - want - tail ulp + 0.5 ulp. */
84*412f47f9SXin Li d = got - want;
85*412f47f9SXin Li e = d > 0 ? -p->tail - 0.5 : -p->tail + 0.5;
86*412f47f9SXin Li }
87*412f47f9SXin Li else
88*412f47f9SXin Li {
89*412f47f9SXin Li if ((r == FE_DOWNWARD && got < want) || (r == FE_UPWARD && got > want)
90*412f47f9SXin Li || (r == FE_TOWARDZERO && fabs (got) < fabs (want)))
91*412f47f9SXin Li got = RT(nextafter) (got, want);
92*412f47f9SXin Li d = got - want;
93*412f47f9SXin Li e = -p->tail;
94*412f47f9SXin Li }
95*412f47f9SXin Li return RT(scalbn) (d, -p->ulpexp) + e;
96*412f47f9SXin Li }
97*412f47f9SXin Li
RT(isok)98*412f47f9SXin Li static int RT(isok) (RT(float) ygot, int exgot, RT(float) ywant, int exwant,
99*412f47f9SXin Li int exmay)
100*412f47f9SXin Li {
101*412f47f9SXin Li return RT(asuint) (ygot) == RT(asuint) (ywant)
102*412f47f9SXin Li && ((exgot ^ exwant) & ~exmay) == 0;
103*412f47f9SXin Li }
104*412f47f9SXin Li
RT(isok_nofenv)105*412f47f9SXin Li static int RT(isok_nofenv) (RT(float) ygot, RT(float) ywant)
106*412f47f9SXin Li {
107*412f47f9SXin Li return RT(asuint) (ygot) == RT(asuint) (ywant);
108*412f47f9SXin Li }
109*412f47f9SXin Li #endif
110*412f47f9SXin Li
T(call_fenv)111*412f47f9SXin Li static inline void T (call_fenv) (const struct fun *f, struct T (args) a,
112*412f47f9SXin Li int r, RT (float) * y, int *ex,
113*412f47f9SXin Li const struct conf *conf)
114*412f47f9SXin Li {
115*412f47f9SXin Li if (r != FE_TONEAREST)
116*412f47f9SXin Li fesetround (r);
117*412f47f9SXin Li feclearexcept (FE_ALL_EXCEPT);
118*412f47f9SXin Li *y = T (call) (f, a, conf);
119*412f47f9SXin Li *ex = fetestexcept (FE_ALL_EXCEPT);
120*412f47f9SXin Li if (r != FE_TONEAREST)
121*412f47f9SXin Li fesetround (FE_TONEAREST);
122*412f47f9SXin Li }
123*412f47f9SXin Li
T(call_nofenv)124*412f47f9SXin Li static inline void T (call_nofenv) (const struct fun *f, struct T (args) a,
125*412f47f9SXin Li int r, RT (float) * y, int *ex,
126*412f47f9SXin Li const struct conf *conf)
127*412f47f9SXin Li {
128*412f47f9SXin Li if (r != FE_TONEAREST)
129*412f47f9SXin Li fesetround (r);
130*412f47f9SXin Li *y = T (call) (f, a, conf);
131*412f47f9SXin Li *ex = 0;
132*412f47f9SXin Li if (r != FE_TONEAREST)
133*412f47f9SXin Li fesetround (FE_TONEAREST);
134*412f47f9SXin Li }
135*412f47f9SXin Li
T(call_long_fenv)136*412f47f9SXin Li static inline int T (call_long_fenv) (const struct fun *f, struct T (args) a,
137*412f47f9SXin Li int r, struct RT (ret) * p,
138*412f47f9SXin Li RT (float) ygot, int exgot)
139*412f47f9SXin Li {
140*412f47f9SXin Li if (r != FE_TONEAREST)
141*412f47f9SXin Li fesetround (r);
142*412f47f9SXin Li feclearexcept (FE_ALL_EXCEPT);
143*412f47f9SXin Li volatile struct T(args) va = a; // TODO: barrier
144*412f47f9SXin Li a = va;
145*412f47f9SXin Li RT(double) yl = T(call_long) (f, a);
146*412f47f9SXin Li p->y = (RT(float)) yl;
147*412f47f9SXin Li volatile RT(float) vy = p->y; // TODO: barrier
148*412f47f9SXin Li (void) vy;
149*412f47f9SXin Li p->ex = fetestexcept (FE_ALL_EXCEPT);
150*412f47f9SXin Li if (r != FE_TONEAREST)
151*412f47f9SXin Li fesetround (FE_TONEAREST);
152*412f47f9SXin Li p->ex_may = FE_INEXACT;
153*412f47f9SXin Li if (RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may))
154*412f47f9SXin Li return 1;
155*412f47f9SXin Li p->ulpexp = RT(ulpscale) (p->y);
156*412f47f9SXin Li if (isinf (p->y))
157*412f47f9SXin Li p->tail = RT(lscalbn) (yl - (RT(double)) 2 * RT(halfinf), -p->ulpexp);
158*412f47f9SXin Li else
159*412f47f9SXin Li p->tail = RT(lscalbn) (yl - p->y, -p->ulpexp);
160*412f47f9SXin Li if (RT(fabs) (p->y) < RT(min_normal))
161*412f47f9SXin Li {
162*412f47f9SXin Li /* TODO: subnormal result is treated as undeflow even if it's
163*412f47f9SXin Li exact since call_long may not raise inexact correctly. */
164*412f47f9SXin Li if (p->y != 0 || (p->ex & FE_INEXACT))
165*412f47f9SXin Li p->ex |= FE_UNDERFLOW | FE_INEXACT;
166*412f47f9SXin Li }
167*412f47f9SXin Li return 0;
168*412f47f9SXin Li }
T(call_long_nofenv)169*412f47f9SXin Li static inline int T(call_long_nofenv) (const struct fun *f, struct T(args) a,
170*412f47f9SXin Li int r, struct RT(ret) * p,
171*412f47f9SXin Li RT(float) ygot, int exgot)
172*412f47f9SXin Li {
173*412f47f9SXin Li if (r != FE_TONEAREST)
174*412f47f9SXin Li fesetround (r);
175*412f47f9SXin Li RT(double) yl = T(call_long) (f, a);
176*412f47f9SXin Li p->y = (RT(float)) yl;
177*412f47f9SXin Li if (r != FE_TONEAREST)
178*412f47f9SXin Li fesetround (FE_TONEAREST);
179*412f47f9SXin Li if (RT(isok_nofenv) (ygot, p->y))
180*412f47f9SXin Li return 1;
181*412f47f9SXin Li p->ulpexp = RT(ulpscale) (p->y);
182*412f47f9SXin Li if (isinf (p->y))
183*412f47f9SXin Li p->tail = RT(lscalbn) (yl - (RT(double)) 2 * RT(halfinf), -p->ulpexp);
184*412f47f9SXin Li else
185*412f47f9SXin Li p->tail = RT(lscalbn) (yl - p->y, -p->ulpexp);
186*412f47f9SXin Li return 0;
187*412f47f9SXin Li }
188*412f47f9SXin Li
189*412f47f9SXin Li /* There are nan input args and all quiet. */
T(qnanpropagation)190*412f47f9SXin Li static inline int T(qnanpropagation) (struct T(args) a)
191*412f47f9SXin Li {
192*412f47f9SXin Li return T(reduce) (a, isnan, ||) && !T(reduce) (a, RT(issignaling), ||);
193*412f47f9SXin Li }
T(sum)194*412f47f9SXin Li static inline RT(float) T(sum) (struct T(args) a)
195*412f47f9SXin Li {
196*412f47f9SXin Li return T(reduce) (a, , +);
197*412f47f9SXin Li }
198*412f47f9SXin Li
199*412f47f9SXin Li /* returns 1 if the got result is ok. */
T(call_mpfr_fix)200*412f47f9SXin Li static inline int T(call_mpfr_fix) (const struct fun *f, struct T(args) a,
201*412f47f9SXin Li int r_fenv, struct RT(ret) * p,
202*412f47f9SXin Li RT(float) ygot, int exgot)
203*412f47f9SXin Li {
204*412f47f9SXin Li #if USE_MPFR
205*412f47f9SXin Li int t, t2;
206*412f47f9SXin Li mpfr_rnd_t r = rmap (r_fenv);
207*412f47f9SXin Li MPFR_DECL_INIT(my, RT(prec_mpfr));
208*412f47f9SXin Li MPFR_DECL_INIT(mr, RT(prec));
209*412f47f9SXin Li MPFR_DECL_INIT(me, RT(prec_mpfr));
210*412f47f9SXin Li mpfr_clear_flags ();
211*412f47f9SXin Li t = T(call_mpfr) (my, f, a, r);
212*412f47f9SXin Li /* Double rounding. */
213*412f47f9SXin Li t2 = mpfr_set (mr, my, r);
214*412f47f9SXin Li if (t2)
215*412f47f9SXin Li t = t2;
216*412f47f9SXin Li mpfr_set_emin (RT(emin));
217*412f47f9SXin Li mpfr_set_emax (RT(emax));
218*412f47f9SXin Li t = mpfr_check_range (mr, t, r);
219*412f47f9SXin Li t = mpfr_subnormalize (mr, t, r);
220*412f47f9SXin Li mpfr_set_emax (MPFR_EMAX_DEFAULT);
221*412f47f9SXin Li mpfr_set_emin (MPFR_EMIN_DEFAULT);
222*412f47f9SXin Li p->y = mpfr_get_d (mr, r);
223*412f47f9SXin Li p->ex = t ? FE_INEXACT : 0;
224*412f47f9SXin Li p->ex_may = FE_INEXACT;
225*412f47f9SXin Li if (mpfr_underflow_p () && (p->ex & FE_INEXACT))
226*412f47f9SXin Li /* TODO: handle before and after rounding uflow cases. */
227*412f47f9SXin Li p->ex |= FE_UNDERFLOW;
228*412f47f9SXin Li if (mpfr_overflow_p ())
229*412f47f9SXin Li p->ex |= FE_OVERFLOW | FE_INEXACT;
230*412f47f9SXin Li if (mpfr_divby0_p ())
231*412f47f9SXin Li p->ex |= FE_DIVBYZERO;
232*412f47f9SXin Li //if (mpfr_erangeflag_p ())
233*412f47f9SXin Li // p->ex |= FE_INVALID;
234*412f47f9SXin Li if (!mpfr_nanflag_p () && RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may))
235*412f47f9SXin Li return 1;
236*412f47f9SXin Li if (mpfr_nanflag_p () && !T(qnanpropagation) (a))
237*412f47f9SXin Li p->ex |= FE_INVALID;
238*412f47f9SXin Li p->ulpexp = RT(ulpscale_mpfr) (my, t);
239*412f47f9SXin Li if (!isfinite (p->y))
240*412f47f9SXin Li {
241*412f47f9SXin Li p->tail = 0;
242*412f47f9SXin Li if (isnan (p->y))
243*412f47f9SXin Li {
244*412f47f9SXin Li /* If an input was nan keep its sign. */
245*412f47f9SXin Li p->y = T(sum) (a);
246*412f47f9SXin Li if (!isnan (p->y))
247*412f47f9SXin Li p->y = (p->y - p->y) / (p->y - p->y);
248*412f47f9SXin Li return RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may);
249*412f47f9SXin Li }
250*412f47f9SXin Li mpfr_set_si_2exp (mr, signbit (p->y) ? -1 : 1, 1024, MPFR_RNDN);
251*412f47f9SXin Li if (mpfr_cmpabs (my, mr) >= 0)
252*412f47f9SXin Li return RT(isok) (ygot, exgot, p->y, p->ex, p->ex_may);
253*412f47f9SXin Li }
254*412f47f9SXin Li mpfr_sub (me, my, mr, MPFR_RNDN);
255*412f47f9SXin Li mpfr_mul_2si (me, me, -p->ulpexp, MPFR_RNDN);
256*412f47f9SXin Li p->tail = mpfr_get_d (me, MPFR_RNDN);
257*412f47f9SXin Li return 0;
258*412f47f9SXin Li #else
259*412f47f9SXin Li abort ();
260*412f47f9SXin Li #endif
261*412f47f9SXin Li }
262*412f47f9SXin Li
T(cmp)263*412f47f9SXin Li static int T(cmp) (const struct fun *f, struct gen *gen,
264*412f47f9SXin Li const struct conf *conf)
265*412f47f9SXin Li {
266*412f47f9SXin Li double maxerr = 0;
267*412f47f9SXin Li uint64_t cnt = 0;
268*412f47f9SXin Li uint64_t cnt1 = 0;
269*412f47f9SXin Li uint64_t cnt2 = 0;
270*412f47f9SXin Li uint64_t cntfail = 0;
271*412f47f9SXin Li int r = conf->r;
272*412f47f9SXin Li int use_mpfr = conf->mpfr;
273*412f47f9SXin Li int fenv = conf->fenv;
274*412f47f9SXin Li
275*412f47f9SXin Li for (;;)
276*412f47f9SXin Li {
277*412f47f9SXin Li struct RT(ret) want;
278*412f47f9SXin Li struct T(args) a = T(next) (gen);
279*412f47f9SXin Li int exgot;
280*412f47f9SXin Li int exgot2;
281*412f47f9SXin Li RT(float) ygot;
282*412f47f9SXin Li RT(float) ygot2;
283*412f47f9SXin Li int fail = 0;
284*412f47f9SXin Li if (fenv)
285*412f47f9SXin Li T (call_fenv) (f, a, r, &ygot, &exgot, conf);
286*412f47f9SXin Li else
287*412f47f9SXin Li T (call_nofenv) (f, a, r, &ygot, &exgot, conf);
288*412f47f9SXin Li if (f->twice) {
289*412f47f9SXin Li secondcall = 1;
290*412f47f9SXin Li if (fenv)
291*412f47f9SXin Li T (call_fenv) (f, a, r, &ygot2, &exgot2, conf);
292*412f47f9SXin Li else
293*412f47f9SXin Li T (call_nofenv) (f, a, r, &ygot2, &exgot2, conf);
294*412f47f9SXin Li secondcall = 0;
295*412f47f9SXin Li if (RT(asuint) (ygot) != RT(asuint) (ygot2))
296*412f47f9SXin Li {
297*412f47f9SXin Li fail = 1;
298*412f47f9SXin Li cntfail++;
299*412f47f9SXin Li T(printcall) (f, a);
300*412f47f9SXin Li printf (" got %a then %a for same input\n", ygot, ygot2);
301*412f47f9SXin Li }
302*412f47f9SXin Li }
303*412f47f9SXin Li cnt++;
304*412f47f9SXin Li int ok = use_mpfr
305*412f47f9SXin Li ? T(call_mpfr_fix) (f, a, r, &want, ygot, exgot)
306*412f47f9SXin Li : (fenv ? T(call_long_fenv) (f, a, r, &want, ygot, exgot)
307*412f47f9SXin Li : T(call_long_nofenv) (f, a, r, &want, ygot, exgot));
308*412f47f9SXin Li if (!ok)
309*412f47f9SXin Li {
310*412f47f9SXin Li int print = 0;
311*412f47f9SXin Li double err = RT (ulperr) (ygot, &want, r, conf->ignore_zero_sign);
312*412f47f9SXin Li double abserr = fabs (err);
313*412f47f9SXin Li // TODO: count errors below accuracy limit.
314*412f47f9SXin Li if (abserr > 0)
315*412f47f9SXin Li cnt1++;
316*412f47f9SXin Li if (abserr > 1)
317*412f47f9SXin Li cnt2++;
318*412f47f9SXin Li if (abserr > conf->errlim)
319*412f47f9SXin Li {
320*412f47f9SXin Li print = 1;
321*412f47f9SXin Li if (!fail)
322*412f47f9SXin Li {
323*412f47f9SXin Li fail = 1;
324*412f47f9SXin Li cntfail++;
325*412f47f9SXin Li }
326*412f47f9SXin Li }
327*412f47f9SXin Li if (abserr > maxerr)
328*412f47f9SXin Li {
329*412f47f9SXin Li maxerr = abserr;
330*412f47f9SXin Li if (!conf->quiet && abserr > conf->softlim)
331*412f47f9SXin Li print = 1;
332*412f47f9SXin Li }
333*412f47f9SXin Li if (print)
334*412f47f9SXin Li {
335*412f47f9SXin Li T(printcall) (f, a);
336*412f47f9SXin Li // TODO: inf ulp handling
337*412f47f9SXin Li printf (" got %a want %a %+g ulp err %g\n", ygot, want.y,
338*412f47f9SXin Li want.tail, err);
339*412f47f9SXin Li }
340*412f47f9SXin Li int diff = fenv ? exgot ^ want.ex : 0;
341*412f47f9SXin Li if (fenv && (diff & ~want.ex_may))
342*412f47f9SXin Li {
343*412f47f9SXin Li if (!fail)
344*412f47f9SXin Li {
345*412f47f9SXin Li fail = 1;
346*412f47f9SXin Li cntfail++;
347*412f47f9SXin Li }
348*412f47f9SXin Li T(printcall) (f, a);
349*412f47f9SXin Li printf (" is %a %+g ulp, got except 0x%0x", want.y, want.tail,
350*412f47f9SXin Li exgot);
351*412f47f9SXin Li if (diff & exgot)
352*412f47f9SXin Li printf (" wrongly set: 0x%x", diff & exgot);
353*412f47f9SXin Li if (diff & ~exgot)
354*412f47f9SXin Li printf (" wrongly clear: 0x%x", diff & ~exgot);
355*412f47f9SXin Li putchar ('\n');
356*412f47f9SXin Li }
357*412f47f9SXin Li }
358*412f47f9SXin Li if (cnt >= conf->n)
359*412f47f9SXin Li break;
360*412f47f9SXin Li if (!conf->quiet && cnt % 0x100000 == 0)
361*412f47f9SXin Li printf ("progress: %6.3f%% cnt %llu cnt1 %llu cnt2 %llu cntfail %llu "
362*412f47f9SXin Li "maxerr %g\n",
363*412f47f9SXin Li 100.0 * cnt / conf->n, (unsigned long long) cnt,
364*412f47f9SXin Li (unsigned long long) cnt1, (unsigned long long) cnt2,
365*412f47f9SXin Li (unsigned long long) cntfail, maxerr);
366*412f47f9SXin Li }
367*412f47f9SXin Li double cc = cnt;
368*412f47f9SXin Li if (cntfail)
369*412f47f9SXin Li printf ("FAIL ");
370*412f47f9SXin Li else
371*412f47f9SXin Li printf ("PASS ");
372*412f47f9SXin Li T(printgen) (f, gen);
373*412f47f9SXin Li printf (" round %c errlim %g maxerr %g %s cnt %llu cnt1 %llu %g%% cnt2 %llu "
374*412f47f9SXin Li "%g%% cntfail %llu %g%%\n",
375*412f47f9SXin Li conf->rc, conf->errlim,
376*412f47f9SXin Li maxerr, conf->r == FE_TONEAREST ? "+0.5" : "+1.0",
377*412f47f9SXin Li (unsigned long long) cnt,
378*412f47f9SXin Li (unsigned long long) cnt1, 100.0 * cnt1 / cc,
379*412f47f9SXin Li (unsigned long long) cnt2, 100.0 * cnt2 / cc,
380*412f47f9SXin Li (unsigned long long) cntfail, 100.0 * cntfail / cc);
381*412f47f9SXin Li return !!cntfail;
382*412f47f9SXin Li }
383