xref: /openbsd-src/regress/lib/libm/fenv/fenv.c (revision d13be5d47e4149db2549a9828e244d59dbc43f15)
1 /*	$OpenBSD: fenv.c,v 1.1 2011/03/21 21:51:28 martynas Exp $	*/
2 
3 /*-
4  * Copyright (c) 2004 David Schultz <das@FreeBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 /*
30  * Test the correctness and C99-compliance of various fenv.h features.
31  */
32 
33 #include <sys/cdefs.h>
34 #include <sys/types.h>
35 #include <sys/wait.h>
36 #include <assert.h>
37 #include <err.h>
38 #include <fenv.h>
39 #include <float.h>
40 #include <math.h>
41 #include <signal.h>
42 #include <stdio.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 /*
47  * Implementations are permitted to define additional exception flags
48  * not specified in the standard, so it is not necessarily true that
49  * FE_ALL_EXCEPT == ALL_STD_EXCEPT.
50  */
51 #define	ALL_STD_EXCEPT	(FE_DIVBYZERO | FE_INEXACT | FE_INVALID | \
52 			 FE_OVERFLOW | FE_UNDERFLOW)
53 
54 #define	NEXCEPTS	(sizeof(std_excepts) / sizeof(std_excepts[0]))
55 
56 static const int std_excepts[] = {
57 	FE_INVALID,
58 	FE_DIVBYZERO,
59 	FE_OVERFLOW,
60 	FE_UNDERFLOW,
61 	FE_INEXACT,
62 };
63 
64 /* init_exceptsets() initializes this to the power set of std_excepts[] */
65 static int std_except_sets[1 << NEXCEPTS];
66 
67 static void init_exceptsets(void);
68 
69 static void test_dfl_env(void);
70 static void test_fegsetenv(void);
71 static void test_fegsetexceptflag(void);
72 static void test_masking(void);
73 static void test_fegsetround(void);
74 static void test_feholdupdate(void);
75 static void test_feraiseexcept(void);
76 static void test_fetestclearexcept(void);
77 
78 static int getround(void);
79 static void raiseexcept(int excepts);
80 static void trap_handler(int sig);
81 
82 #pragma STDC FENV_ACCESS ON
83 
84 int
85 main(int argc, char *argv[])
86 {
87 
88 	printf("1..8\n");
89 	init_exceptsets();
90 	test_dfl_env();
91 	printf("ok 1 - fenv\n");
92 	test_fetestclearexcept();
93 	printf("ok 2 - fenv\n");
94 	test_fegsetexceptflag();
95 	printf("ok 3 - fenv\n");
96 	test_feraiseexcept();
97 	printf("ok 4 - fenv\n");
98 	test_fegsetround();
99 	printf("ok 5 - fenv\n");
100 	test_fegsetenv();
101 	printf("ok 6 - fenv\n");
102 	test_masking();
103 	printf("ok 7 - fenv\n");
104 	test_feholdupdate();
105 	printf("ok 8 - fenv\n");
106 
107 	return (0);
108 }
109 
110 /*
111  * Initialize std_except_sets[] to the power set of std_excepts[]
112  */
113 void
114 init_exceptsets(void)
115 {
116 	int i, j, sr;
117 
118 	for (i = 0; i < 1 << NEXCEPTS; i++) {
119 		for (sr = i, j = 0; sr != 0; sr >>= 1, j++)
120 			std_except_sets[i] |= std_excepts[j] & ((~sr & 1) - 1);
121 	}
122 }
123 
124 /*
125  * This tests checks the default FP environment, so it must be first.
126  * The memcmp() test below may be too much to ask for, since there
127  * could be multiple machine-specific default environments.
128  */
129 static void
130 test_dfl_env(void)
131 {
132 #ifndef NO_STRICT_DFL_ENV
133 	fenv_t env;
134 
135 	fegetenv(&env);
136 	assert(memcmp(&env, FE_DFL_ENV, sizeof(env)) == 0);
137 #endif
138 	assert(fetestexcept(FE_ALL_EXCEPT) == 0);
139 }
140 
141 /*
142  * Test fetestexcept() and feclearexcept().
143  */
144 static void
145 test_fetestclearexcept(void)
146 {
147 	int excepts, i;
148 
149 	for (i = 0; i < 1 << NEXCEPTS; i++)
150 		assert(fetestexcept(std_except_sets[i]) == 0);
151 	for (i = 0; i < 1 << NEXCEPTS; i++) {
152 		excepts = std_except_sets[i];
153 
154 		/* FE_ALL_EXCEPT might be special-cased, as on i386. */
155 		raiseexcept(excepts);
156 		assert(fetestexcept(excepts) == excepts);
157 		assert(feclearexcept(FE_ALL_EXCEPT) == 0);
158 		assert(fetestexcept(FE_ALL_EXCEPT) == 0);
159 
160 		raiseexcept(excepts);
161 		assert(fetestexcept(excepts) == excepts);
162 		if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0) {
163 			excepts |= FE_INEXACT;
164 			assert((fetestexcept(ALL_STD_EXCEPT) | FE_INEXACT) ==
165 			    excepts);
166 		} else {
167 			assert(fetestexcept(ALL_STD_EXCEPT) == excepts);
168 		}
169 		assert(feclearexcept(excepts) == 0);
170 		assert(fetestexcept(ALL_STD_EXCEPT) == 0);
171 	}
172 }
173 
174 /*
175  * Test fegetexceptflag() and fesetexceptflag().
176  *
177  * Prerequisites: fetestexcept(), feclearexcept()
178  */
179 static void
180 test_fegsetexceptflag(void)
181 {
182 	fexcept_t flag;
183 	int excepts, i;
184 
185 	assert(fetestexcept(FE_ALL_EXCEPT) == 0);
186 	for (i = 0; i < 1 << NEXCEPTS; i++) {
187 		excepts = std_except_sets[i];
188 
189 		assert(fegetexceptflag(&flag, excepts) == 0);
190 		raiseexcept(ALL_STD_EXCEPT);
191 		assert(fesetexceptflag(&flag, excepts) == 0);
192 		assert(fetestexcept(ALL_STD_EXCEPT) ==
193 		    (ALL_STD_EXCEPT ^ excepts));
194 
195 		assert(fegetexceptflag(&flag, FE_ALL_EXCEPT) == 0);
196 		assert(feclearexcept(FE_ALL_EXCEPT) == 0);
197 		assert(fesetexceptflag(&flag, excepts) == 0);
198 		assert(fetestexcept(ALL_STD_EXCEPT) == 0);
199 		assert(fesetexceptflag(&flag, ALL_STD_EXCEPT ^ excepts) == 0);
200 		assert(fetestexcept(ALL_STD_EXCEPT) ==
201 		    (ALL_STD_EXCEPT ^ excepts));
202 
203 		assert(feclearexcept(FE_ALL_EXCEPT) == 0);
204 	}
205 }
206 
207 /*
208  * Test feraiseexcept().
209  *
210  * Prerequisites: fetestexcept(), feclearexcept()
211  */
212 static void
213 test_feraiseexcept(void)
214 {
215 	int excepts, i;
216 
217 	for (i = 0; i < 1 << NEXCEPTS; i++) {
218 		excepts = std_except_sets[i];
219 
220 		assert(fetestexcept(FE_ALL_EXCEPT) == 0);
221 		assert(feraiseexcept(excepts) == 0);
222 		if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0) {
223 			excepts |= FE_INEXACT;
224 			assert((fetestexcept(ALL_STD_EXCEPT) | FE_INEXACT) ==
225 			    excepts);
226 		} else {
227 			assert(fetestexcept(ALL_STD_EXCEPT) == excepts);
228 		}
229 		assert(feclearexcept(FE_ALL_EXCEPT) == 0);
230 	}
231 	assert(feraiseexcept(FE_INVALID | FE_DIVBYZERO) == 0);
232 	assert(fetestexcept(ALL_STD_EXCEPT) == (FE_INVALID | FE_DIVBYZERO));
233 	assert(feraiseexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT) == 0);
234 	assert(fetestexcept(ALL_STD_EXCEPT) == ALL_STD_EXCEPT);
235 	assert(feclearexcept(FE_ALL_EXCEPT) == 0);
236 }
237 
238 /*
239  * Test fegetround() and fesetround().
240  */
241 static void
242 test_fegsetround(void)
243 {
244 
245 	assert(fegetround() == FE_TONEAREST);
246 	assert(getround() == FE_TONEAREST);
247 	assert(FLT_ROUNDS == 1);
248 
249 	assert(fesetround(FE_DOWNWARD) == 0);
250 	assert(fegetround() == FE_DOWNWARD);
251 	assert(getround() == FE_DOWNWARD);
252 	assert(FLT_ROUNDS == 3);
253 
254 	assert(fesetround(FE_UPWARD) == 0);
255 	assert(getround() == FE_UPWARD);
256 	assert(fegetround() == FE_UPWARD);
257 	assert(FLT_ROUNDS == 2);
258 
259 	assert(fesetround(FE_TOWARDZERO) == 0);
260 	assert(getround() == FE_TOWARDZERO);
261 	assert(fegetround() == FE_TOWARDZERO);
262 	assert(FLT_ROUNDS == 0);
263 
264 	assert(fesetround(FE_TONEAREST) == 0);
265 	assert(getround() == FE_TONEAREST);
266 	assert(FLT_ROUNDS == 1);
267 
268 	assert(feclearexcept(FE_ALL_EXCEPT) == 0);
269 }
270 
271 /*
272  * Test fegetenv() and fesetenv().
273  *
274  * Prerequisites: fetestexcept(), feclearexcept(), fegetround(), fesetround()
275  */
276 static void
277 test_fegsetenv(void)
278 {
279 	fenv_t env1, env2;
280 	int excepts, i;
281 
282 	for (i = 0; i < 1 << NEXCEPTS; i++) {
283 		excepts = std_except_sets[i];
284 
285 		assert(fetestexcept(FE_ALL_EXCEPT) == 0);
286 		assert(fegetround() == FE_TONEAREST);
287 		assert(fegetenv(&env1) == 0);
288 
289 		/*
290 		 * fe[gs]etenv() should be able to save and restore
291 		 * exception flags without the spurious inexact
292 		 * exceptions that afflict raiseexcept().
293 		 */
294 		raiseexcept(excepts);
295 		if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0 &&
296 		    (excepts & FE_INEXACT) == 0)
297 			assert(feclearexcept(FE_INEXACT) == 0);
298 
299 		fesetround(FE_DOWNWARD);
300 		assert(fegetenv(&env2) == 0);
301 		assert(fesetenv(&env1) == 0);
302 		assert(fetestexcept(FE_ALL_EXCEPT) == 0);
303 		assert(fegetround() == FE_TONEAREST);
304 
305 		assert(fesetenv(&env2) == 0);
306 		assert(fetestexcept(FE_ALL_EXCEPT) == excepts);
307 		assert(fegetround() == FE_DOWNWARD);
308 		assert(fesetenv(&env1) == 0);
309 		assert(fetestexcept(FE_ALL_EXCEPT) == 0);
310 		assert(fegetround() == FE_TONEAREST);
311 	}
312 }
313 
314 /*
315  * Test fegetexcept(), fedisableexcept(), and feenableexcept().
316  *
317  * Prerequisites: fetestexcept(), feraiseexcept()
318  */
319 static void
320 test_masking(void)
321 {
322 	struct sigaction act;
323 	int except, i, pass, raise, status;
324 
325 	assert((fegetexcept() & ALL_STD_EXCEPT) == 0);
326 	assert((feenableexcept(FE_INVALID|FE_OVERFLOW) & ALL_STD_EXCEPT) == 0);
327 	assert((feenableexcept(FE_UNDERFLOW) & ALL_STD_EXCEPT) ==
328 	    (FE_INVALID | FE_OVERFLOW));
329 	assert((fedisableexcept(FE_OVERFLOW) & ALL_STD_EXCEPT) ==
330 	    (FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW));
331 	assert((fegetexcept() & ALL_STD_EXCEPT) == (FE_INVALID | FE_UNDERFLOW));
332 	assert((fedisableexcept(FE_ALL_EXCEPT) & ALL_STD_EXCEPT) ==
333 	    (FE_INVALID | FE_UNDERFLOW));
334 	assert((fegetexcept() & ALL_STD_EXCEPT) == 0);
335 
336 	sigemptyset(&act.sa_mask);
337 	act.sa_flags = 0;
338 	act.sa_handler = trap_handler;
339 	for (pass = 0; pass < 2; pass++) {
340 		for (i = 0; i < NEXCEPTS; i++) {
341 			except = std_excepts[i];
342 			/* over/underflow may also raise inexact */
343 			if (except == FE_INEXACT)
344 				raise = FE_DIVBYZERO | FE_INVALID;
345 			else
346 				raise = ALL_STD_EXCEPT ^ except;
347 
348 			/*
349 			 * We need to fork a child process because
350 			 * there isn't a portable way to recover from
351 			 * a floating-point exception.
352 			 */
353 			switch(fork()) {
354 			case 0:		/* child */
355 				assert((fegetexcept() & ALL_STD_EXCEPT) == 0);
356 				assert((feenableexcept(except)
357 					   & ALL_STD_EXCEPT) == 0);
358 				assert(fegetexcept() == except);
359 				raiseexcept(raise);
360 				assert(feraiseexcept(raise) == 0);
361 				assert(fetestexcept(ALL_STD_EXCEPT) == raise);
362 
363 				assert(sigaction(SIGFPE, &act, NULL) == 0);
364 				switch (pass) {
365 				case 0:
366 					raiseexcept(except);
367 				case 1:
368 					feraiseexcept(except);
369 				default:
370 					assert(0);
371 				}
372 				assert(0);
373 			default:	/* parent */
374 				assert(wait(&status) > 0);
375 				/*
376 				 * Avoid assert() here so that it's possible
377 				 * to examine a failed child's core dump.
378 				 */
379 				if (!WIFEXITED(status))
380 					errx(1, "child aborted\n");
381 				assert(WEXITSTATUS(status) == 0);
382 				break;
383 			case -1:	/* error */
384 				assert(0);
385 			}
386 		}
387 	}
388 	assert(fetestexcept(FE_ALL_EXCEPT) == 0);
389 }
390 
391 /*
392  * Test feholdexcept() and feupdateenv().
393  *
394  * Prerequisites: fetestexcept(), fegetround(), fesetround(),
395  *	fedisableexcept(), feenableexcept()
396  */
397 static void
398 test_feholdupdate(void)
399 {
400 	fenv_t env;
401 
402 	struct sigaction act;
403 	int except, i, pass, status, raise;
404 
405 	sigemptyset(&act.sa_mask);
406 	act.sa_flags = 0;
407 	act.sa_handler = trap_handler;
408 	for (pass = 0; pass < 2; pass++) {
409 		for (i = 0; i < NEXCEPTS; i++) {
410 			except = std_excepts[i];
411 			/* over/underflow may also raise inexact */
412 			if (except == FE_INEXACT)
413 				raise = FE_DIVBYZERO | FE_INVALID;
414 			else
415 				raise = ALL_STD_EXCEPT ^ except;
416 
417 			/*
418 			 * We need to fork a child process because
419 			 * there isn't a portable way to recover from
420 			 * a floating-point exception.
421 			 */
422 			switch(fork()) {
423 			case 0:		/* child */
424 				/*
425 				 * We don't want to cause a fatal exception in
426 				 * the child until the second pass, so we can
427 				 * check other properties of feupdateenv().
428 				 */
429 				if (pass == 1)
430 					assert((feenableexcept(except) &
431 						   ALL_STD_EXCEPT) == 0);
432 				raiseexcept(raise);
433 				assert(fesetround(FE_DOWNWARD) == 0);
434 				assert(feholdexcept(&env) == 0);
435 				assert(fetestexcept(FE_ALL_EXCEPT) == 0);
436 				raiseexcept(except);
437 				assert(fesetround(FE_UPWARD) == 0);
438 
439 				if (pass == 1)
440 					assert(sigaction(SIGFPE, &act, NULL) ==
441 					    0);
442 				assert(feupdateenv(&env) == 0);
443 				assert(fegetround() == FE_DOWNWARD);
444 				assert(fetestexcept(ALL_STD_EXCEPT) ==
445 				    (except | raise));
446 
447 				assert(pass == 0);
448 				_exit(0);
449 			default:	/* parent */
450 				assert(wait(&status) > 0);
451 				/*
452 				 * Avoid assert() here so that it's possible
453 				 * to examine a failed child's core dump.
454 				 */
455 				if (!WIFEXITED(status))
456 					errx(1, "child aborted\n");
457 				assert(WEXITSTATUS(status) == 0);
458 				break;
459 			case -1:	/* error */
460 				assert(0);
461 			}
462 		}
463 	}
464 	assert(fetestexcept(FE_ALL_EXCEPT) == 0);
465 }
466 
467 /*
468  * Raise a floating-point exception without relying on the standard
469  * library routines, which we are trying to test.
470  *
471  * XXX We can't raise an {over,under}flow without also raising an
472  * inexact exception.
473  */
474 static void
475 raiseexcept(int excepts)
476 {
477 	volatile double d;
478 
479 	/*
480 	 * With a compiler that supports the FENV_ACCESS pragma
481 	 * properly, simple expressions like '0.0 / 0.0' should
482 	 * be sufficient to generate traps.  Unfortunately, we
483 	 * need to bring a volatile variable into the equation
484 	 * to prevent incorrect optimizations.
485 	 */
486 	if (excepts & FE_INVALID) {
487 		d = 0.0;
488 		d = 0.0 / d;
489 	}
490 	if (excepts & FE_DIVBYZERO) {
491 		d = 0.0;
492 		d = 1.0 / d;
493 	}
494 	if (excepts & FE_OVERFLOW) {
495 		d = DBL_MAX;
496 		d *= 2.0;
497 	}
498 	if (excepts & FE_UNDERFLOW) {
499 		d = DBL_MIN;
500 		d /= DBL_MAX;
501 	}
502 	if (excepts & FE_INEXACT) {
503 		d = DBL_MIN;
504 		d += 1.0;
505 	}
506 
507 	/*
508 	 * On the x86 (and some other architectures?) the FPU and
509 	 * integer units are decoupled.  We need to execute an FWAIT
510 	 * or a floating-point instruction to get synchronous exceptions.
511 	 */
512 	d = 1.0;
513 	d += 1.0;
514 }
515 
516 /*
517  * Determine the current rounding mode without relying on the fenv
518  * routines.  This function may raise an inexact exception.
519  */
520 static int
521 getround(void)
522 {
523 	volatile double d;
524 
525 	/*
526 	 * This test works just as well with 0.0 - 0.0, except on ia64
527 	 * where 0.0 - 0.0 gives the wrong sign when rounding downwards.
528 	 */
529 	d = 1.0;
530 	d -= 1.0;
531 	if (copysign(1.0, d) < 0.0)
532 		return (FE_DOWNWARD);
533 
534 	d = 1.0;
535 	if (d + (DBL_EPSILON * 3.0 / 4.0) == 1.0)
536 		return (FE_TOWARDZERO);
537 	if (d + (DBL_EPSILON * 1.0 / 4.0) > 1.0)
538 		return (FE_UPWARD);
539 
540 	return (FE_TONEAREST);
541 }
542 
543 static void
544 trap_handler(int sig)
545 {
546 
547 	assert(sig == SIGFPE);
548 	_exit(0);
549 }
550