t_fe_round.c revision 1.21 1 /* $NetBSD: t_fe_round.c,v 1.21 2025/04/17 13:45:22 riastradh Exp $ */
2
3 /*
4 * Written by Maya Rashish <maya (at) NetBSD.org>
5 * Public domain.
6 *
7 * Testing IEEE-754 rounding modes (and lrint)
8 */
9
10 #include <sys/cdefs.h>
11 __RCSID("$NetBSD: t_fe_round.c,v 1.21 2025/04/17 13:45:22 riastradh Exp $");
12
13 #include <atf-c.h>
14 #include <fenv.h>
15 #include <math.h>
16 #include <stdint.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19
20 #ifdef __HAVE_FENV
21
22 /*#pragma STDC FENV_ACCESS ON gcc?? */
23
24 #define INT 9223L
25
26 static const char *
27 rmname(int rm)
28 {
29 switch (rm) {
30 case FE_TOWARDZERO:
31 return "FE_TOWARDZERO";
32 case FE_DOWNWARD:
33 return "FE_DOWNWARD";
34 case FE_UPWARD:
35 return "FE_UPWARD";
36 case FE_TONEAREST:
37 return "FE_TONEAREST";
38 default:
39 return "unknown";
40 }
41 }
42
43 /*
44 * Examples are chosen to fit within the smallest single-precision
45 * format any NetBSD port uses, so that we can write the examples once
46 * in type double, and convert to single without raising inexact-result
47 * exceptions when we're trying to test whether the integer-rounding
48 * functions raise them.
49 */
50 static const struct {
51 int round_mode;
52 double input;
53 long int expected;
54 } values[] = {
55 { FE_DOWNWARD, 3.75, 3},
56 { FE_DOWNWARD, -3.75, -4},
57 { FE_DOWNWARD, +0., 0},
58 { FE_DOWNWARD, -0., 0},
59 { FE_DOWNWARD, -INT-0.0625, -INT-1},
60 { FE_DOWNWARD, +INT-0.0625, INT-1},
61 { FE_DOWNWARD, -INT+0.0625, -INT},
62 { FE_DOWNWARD, +INT+0.0625, INT},
63
64 { FE_UPWARD, +0., 0},
65 { FE_UPWARD, -0., 0},
66 { FE_UPWARD, -123.75, -123},
67 { FE_UPWARD, 123.75, 124},
68 { FE_UPWARD, -INT-0.0625, -INT},
69 { FE_UPWARD, +INT-0.0625, INT},
70 { FE_UPWARD, -INT+0.0625, -INT+1},
71 { FE_UPWARD, +INT+0.0625, INT+1},
72
73 { FE_TOWARDZERO, 1.9375, 1},
74 { FE_TOWARDZERO, -1.9375, -1},
75 { FE_TOWARDZERO, 0.25, 0},
76 { FE_TOWARDZERO, INT+0.0625, INT},
77 { FE_TOWARDZERO, INT-0.0625, INT - 1},
78 { FE_TOWARDZERO, -INT+0.0625, -INT + 1},
79 { FE_TOWARDZERO, +0., 0},
80 { FE_TOWARDZERO, -0., 0},
81
82 { FE_TONEAREST, -INT-0.0625, -INT},
83 { FE_TONEAREST, +INT-0.0625, INT},
84 { FE_TONEAREST, -INT+0.0625, -INT},
85 { FE_TONEAREST, +INT+0.0625, INT},
86 { FE_TONEAREST, -INT-0.53125, -INT-1},
87 { FE_TONEAREST, +INT-0.53125, INT-1},
88 { FE_TONEAREST, -INT+0.53125, -INT+1},
89 { FE_TONEAREST, +INT+0.53125, INT+1},
90 { FE_TONEAREST, +0., 0},
91 { FE_TONEAREST, -0., 0},
92 };
93
94 ATF_TC(fe_lrint);
95 ATF_TC_HEAD(fe_lrint, tc)
96 {
97 atf_tc_set_md_var(tc, "descr",
98 "Checking IEEE 754 rounding modes using lrint(3)");
99 }
100
101 ATF_TC_BODY(fe_lrint, tc)
102 {
103 enum {
104 LLRINT,
105 LLRINTF,
106 LRINT,
107 LRINTF,
108 N_FN,
109 } fn;
110 static const char *const fnname[] = {
111 [LLRINT] = "llrint",
112 [LLRINTF] = "llrintf",
113 [LRINT] = "lrint",
114 [LRINTF] = "lrintf",
115 };
116 long int received;
117 unsigned i;
118
119 for (i = 0; i < __arraycount(values); i++) {
120 for (fn = 0; fn < N_FN; fn++) {
121 /*
122 * Set the requested rounding mode.
123 */
124 fesetround(values[i].round_mode);
125
126 /*
127 * Call the lrint(3)-family function.
128 */
129 switch (fn) {
130 case LLRINT:
131 received = llrint(values[i].input);
132 break;
133 case LLRINTF:
134 received = llrintf(values[i].input);
135 break;
136 case LRINT:
137 received = lrint(values[i].input);
138 break;
139 case LRINTF:
140 received = lrintf(values[i].input);
141 break;
142 default:
143 atf_tc_fail("impossible");
144 }
145
146 /*
147 * Assuming the result we got has zero
148 * fractional part, casting to long int should
149 * have no rounding. Verify it matches the
150 * integer we expect.
151 */
152 ATF_CHECK_MSG((long int)received == values[i].expected,
153 "[%u] %s %s(%f): got %ld, expected %ld",
154 i, rmname(values[i].round_mode), fnname[fn],
155 values[i].input,
156 (long int)received, values[i].expected);
157
158 /* Do we get the same rounding mode out? */
159 ATF_CHECK_MSG(fegetround() == values[i].round_mode,
160 "[%u] %s: set %d (%s), got %d (%s)",
161 i, fnname[fn],
162 values[i].round_mode, rmname(values[i].round_mode),
163 fegetround(), rmname(fegetround()));
164 }
165 }
166 }
167
168 ATF_TC(fe_nearbyint_rint);
169 ATF_TC_HEAD(fe_nearbyint_rint, tc)
170 {
171 atf_tc_set_md_var(tc, "descr",
172 "Checking IEEE 754 rounding modes using nearbyint/rint");
173 }
174
175 ATF_TC_BODY(fe_nearbyint_rint, tc)
176 {
177 enum {
178 NEARBYINT,
179 NEARBYINTF,
180 NEARBYINTL,
181 RINT,
182 RINTF,
183 RINTL,
184 N_FN,
185 } fn;
186 static const char *const fnname[] = {
187 [NEARBYINT] = "nearbyint",
188 [NEARBYINTF] = "nearbyintf",
189 [NEARBYINTL] = "nearbyintl",
190 [RINT] = "rint",
191 [RINTF] = "rintf",
192 [RINTL] = "rintl",
193 };
194 double received, ipart, fpart;
195 unsigned i;
196
197 #ifdef __sparc64__
198 atf_tc_expect_fail("PR port-sparc64/59310:"
199 " t_fe_round:fe_nearbyint_rint tests are failing");
200 #endif
201
202 for (i = 0; i < __arraycount(values); i++) {
203 for (fn = 0; fn < N_FN; fn++) {
204 bool expect_except =
205 values[i].input != (double)values[i].expected;
206
207 /*
208 * Set the requested rounding mode.
209 */
210 fesetround(values[i].round_mode);
211
212 #ifdef __ia64__
213 /*
214 * Without this barrier, we get:
215 *
216 * /tmp//ccJayu9g.s:2793: Warning: Use of 'mov.m' violates RAW dependency 'AR[FPSR].sf0.flags' (impliedf)
217 * /tmp//ccJayu9g.s:2793: Warning: Only the first path encountering the conflict is reported
218 * /tmp//ccJayu9g.s:2757: Warning: This is the location of the conflicting usage
219 *
220 * (If you fix this, remove the entry from doc/HACKS.)
221 */
222 __insn_barrier();
223 #endif
224
225 /*
226 * Clear sticky floating-point exception bits
227 * so we can verify whether the FE_INEXACT
228 * exception is raised.
229 */
230 feclearexcept(FE_ALL_EXCEPT);
231
232 /*
233 * Call the rint(3)-family function.
234 */
235 switch (fn) {
236 case NEARBYINT:
237 received = nearbyint(values[i].input);
238 expect_except = false;
239 break;
240 case NEARBYINTF:
241 received = nearbyintf(values[i].input);
242 expect_except = false;
243 break;
244 case NEARBYINTL:
245 received = nearbyintl(values[i].input);
246 expect_except = false;
247 break;
248 case RINT:
249 received = rint(values[i].input);
250 break;
251 case RINTF:
252 received = rintf(values[i].input);
253 break;
254 case RINTL:
255 received = rintl(values[i].input);
256 break;
257 default:
258 atf_tc_fail("impossible");
259 }
260
261 /*
262 * Verify FE_INEXACT was raised or not,
263 * depending on whether there was rounding and
264 * whether the function is supposed to raise
265 * exceptions.
266 */
267 if (expect_except) {
268 ATF_CHECK_MSG(fetestexcept(FE_INEXACT) != 0,
269 "[%u] %s %s(%f)"
270 " failed to raise FE_INEXACT",
271 i, rmname(values[i].round_mode),
272 fnname[fn], values[i].input);
273 } else {
274 ATF_CHECK_MSG(fetestexcept(FE_INEXACT) == 0,
275 "[%u] %s %s(%f)"
276 " spuriously raised FE_INEXACT",
277 i, rmname(values[i].round_mode),
278 fnname[fn], values[i].input);
279 }
280
281 /*
282 * Verify the fractional part of the result is
283 * zero -- the result of rounding to an integer
284 * is supposed to be an integer.
285 */
286 fpart = modf(received, &ipart);
287 ATF_CHECK_MSG(fpart == 0,
288 "[%u] %s %s(%f)=%f has fractional part %f"
289 " (integer part %f)",
290 i, rmname(values[i].round_mode), fnname[fn],
291 values[i].input, received, fpart, ipart);
292
293 /*
294 * Assuming the result we got has zero
295 * fractional part, casting to long int should
296 * have no rounding. Verify it matches the
297 * integer we expect.
298 */
299 ATF_CHECK_MSG((long int)received == values[i].expected,
300 "[%u] %s %s(%f): got %f, expected %ld",
301 i, rmname(values[i].round_mode), fnname[fn],
302 values[i].input, received, values[i].expected);
303
304 /* Do we get the same rounding mode out? */
305 ATF_CHECK_MSG(fegetround() == values[i].round_mode,
306 "[%u] %s: set %d (%s), got %d (%s)",
307 i, fnname[fn],
308 values[i].round_mode, rmname(values[i].round_mode),
309 fegetround(), rmname(fegetround()));
310 }
311 }
312 }
313
314 #ifdef __HAVE_LONG_DOUBLE
315
316 /*
317 * Use one bit more than fits in IEEE 754 binary64.
318 */
319 static const struct {
320 int round_mode;
321 long double input;
322 int64_t expected;
323 } valuesl[] = {
324 { FE_TOWARDZERO, 0x2.00000000000008p+52L, 0x20000000000000 },
325 { FE_DOWNWARD, 0x2.00000000000008p+52L, 0x20000000000000 },
326 { FE_UPWARD, 0x2.00000000000008p+52L, 0x20000000000001 },
327 { FE_TONEAREST, 0x2.00000000000008p+52L, 0x20000000000000 },
328 { FE_TOWARDZERO, 0x2.00000000000018p+52L, 0x20000000000001 },
329 { FE_DOWNWARD, 0x2.00000000000018p+52L, 0x20000000000001 },
330 { FE_UPWARD, 0x2.00000000000018p+52L, 0x20000000000002 },
331 { FE_TONEAREST, 0x2.00000000000018p+52L, 0x20000000000002 },
332 };
333
334 ATF_TC(fe_nearbyintl_rintl);
335 ATF_TC_HEAD(fe_nearbyintl_rintl, tc)
336 {
337 atf_tc_set_md_var(tc, "descr",
338 "Checking IEEE 754 rounding modes using nearbyintl/rintl");
339 }
340
341 ATF_TC_BODY(fe_nearbyintl_rintl, tc)
342 {
343 enum {
344 RINTL,
345 NEARBYINTL,
346 N_FN,
347 } fn;
348 static const char *const fnname[] = {
349 [RINTL] = "rintl",
350 [NEARBYINTL] = "nearbyintl",
351 };
352 long double received, ipart, fpart;
353 unsigned i;
354
355 #ifdef __sparc64__
356 atf_tc_expect_fail("PR port-sparc64/59310:"
357 " t_fe_round:fe_nearbyint_rint tests are failing");
358 #endif
359
360 for (i = 0; i < __arraycount(valuesl); i++) {
361 for (fn = 0; fn < N_FN; fn++) {
362 bool expect_except =
363 (valuesl[i].input !=
364 (long double)valuesl[i].expected);
365
366 /*
367 * Set the requested rounding mode.
368 */
369 fesetround(valuesl[i].round_mode);
370
371 /*
372 * Clear sticky floating-point exception bits
373 * so we can verify whether the FE_INEXACT
374 * exception is raised.
375 */
376 feclearexcept(FE_ALL_EXCEPT);
377
378 /*
379 * Call the rint(3)-family function.
380 */
381 switch (fn) {
382 case NEARBYINTL:
383 received = nearbyintl(valuesl[i].input);
384 expect_except = false;
385 break;
386 case RINTL:
387 received = rintl(valuesl[i].input);
388 break;
389 default:
390 atf_tc_fail("impossible");
391 }
392
393 /*
394 * Verify FE_INEXACT was raised or not,
395 * depending on whether there was rounding and
396 * whether the function is supposed to raise
397 * exceptions.
398 */
399 if (expect_except) {
400 ATF_CHECK_MSG(fetestexcept(FE_INEXACT) != 0,
401 "[%u] %s %s(%Lf)"
402 " failed to raise FE_INEXACT",
403 i, rmname(valuesl[i].round_mode),
404 fnname[fn], valuesl[i].input);
405 } else {
406 ATF_CHECK_MSG(fetestexcept(FE_INEXACT) == 0,
407 "[%u] %s %s(%Lf)"
408 " spuriously raised FE_INEXACT",
409 i, rmname(valuesl[i].round_mode),
410 fnname[fn], valuesl[i].input);
411 }
412
413 /*
414 * Verify the fractional part of the result is
415 * zero -- the result of rounding to an integer
416 * is supposed to be an integer.
417 */
418 fpart = modfl(received, &ipart);
419 ATF_CHECK_MSG(fpart == 0,
420 "[%u] %s %s(%Lf)=%Lf has fractional part %Lf"
421 " (integer part %Lf)",
422 i, rmname(valuesl[i].round_mode), fnname[fn],
423 valuesl[i].input, received, fpart, ipart);
424
425 /*
426 * Assuming the result we got has zero
427 * fractional part, casting to int64_t should
428 * have no rounding. Verify it matches the
429 * integer we expect.
430 */
431 ATF_CHECK_MSG(((int64_t)received ==
432 valuesl[i].expected),
433 "[%u] %s %s(%Lf): got %Lf, expected %jd",
434 i, rmname(valuesl[i].round_mode), fnname[fn],
435 valuesl[i].input, received, valuesl[i].expected);
436
437 /* Do we get the same rounding mode out? */
438 ATF_CHECK_MSG(fegetround() == valuesl[i].round_mode,
439 "[%u] %s: set %d (%s), got %d (%s)",
440 i, fnname[fn],
441 valuesl[i].round_mode,
442 rmname(valuesl[i].round_mode),
443 fegetround(), rmname(fegetround()));
444 }
445 }
446 }
447
448 #endif /* __HAVE_LONG_DOUBLE */
449
450 ATF_TP_ADD_TCS(tp)
451 {
452
453 ATF_TP_ADD_TC(tp, fe_lrint);
454 ATF_TP_ADD_TC(tp, fe_nearbyint_rint);
455 #ifdef __HAVE_LONG_DOUBLE
456 ATF_TP_ADD_TC(tp, fe_nearbyintl_rintl);
457 #endif
458
459 return atf_no_error();
460 }
461
462 #else /* !__HAVE_FENV */
463
464 ATF_TP_ADD_TCS(tp)
465 {
466
467 /*
468 * No fenv, no fesetround to test.
469 */
470 return atf_no_error();
471 }
472
473 #endif /* __HAVE_FENV */
474