exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / fenv-round.c
blobf9e1082474bf4ae262bbfe0edabde0e0f764e12a
1 /* Functions for controlling the floating-point rounding direction.
2 Copyright (C) 1997-2024 Free Software Foundation, Inc.
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17 /* Based on glibc/sysdeps/<cpu>/{fegetround.c,fesetround.c}
18 together with glibc/sysdeps/<cpu>/{fpu_control.h,fenv_private.h,fenv_libc.h}. */
20 #include <config.h>
22 /* Specification. */
23 #include <fenv.h>
25 #include "fenv-private.h"
27 #if defined __GNUC__ || defined __clang__ || defined _MSC_VER
29 # if (defined __x86_64__ || defined _M_X64) || (defined __i386 || defined _M_IX86)
31 int
32 fegetround (void)
34 # ifdef _MSC_VER
35 /* Use the rounding direction from the SSE unit. */
36 unsigned int mxcsr;
37 _FPU_GETSSECW (mxcsr);
38 unsigned int fctrl = (mxcsr >> 3) & 0x0C00;
39 # else
40 /* Use the rounding direction from the control word of the 387 unit, the
41 so-called fctrl register.
42 The rounding direction of the SSE unit, in the mxcsr register, is expected
43 to be in sync with that. */
44 unsigned short fctrl;
45 _FPU_GETCW (fctrl);
46 # endif
47 # ifdef _MSC_VER
48 /* The MSVC header files have different values for the rounding directions
49 than all the other platforms, and the even changed between MSVC 14 and
50 MSVC 14.30 (!). Map
51 0x0000 -> FE_TONEAREST = 0
52 0x0400 -> FE_DOWNWARD
53 0x0800 -> FE_UPWARD
54 0x0C00 -> FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD */
55 return (fctrl & 0x0800 ? FE_UPWARD : 0) | (fctrl & 0x0400 ? FE_DOWNWARD : 0);
56 # else
57 return fctrl & 0x0C00;
58 # endif
61 int
62 fesetround (int rounding_direction)
64 # ifdef _MSC_VER
65 /* The MSVC header files have different values for the rounding directions
66 than all the other platforms. */
67 if ((rounding_direction & ~0x0300) != 0)
68 return -1;
69 /* The MSVC header files have different values for the rounding directions
70 than all the other platforms, and the even changed between MSVC 14 and
71 MSVC 14.30 (!). Map
72 FE_TONEAREST = 0 -> 0x0000
73 FE_DOWNWARD -> 0x0400
74 FE_UPWARD -> 0x0800
75 FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD -> 0x0C00 */
76 rounding_direction =
77 (rounding_direction & FE_UPWARD ? 0x0800 : 0)
78 | (rounding_direction & FE_DOWNWARD ? 0x0400 : 0);
79 # else
80 if ((rounding_direction & ~0x0C00) != 0)
81 return -1;
82 # endif
84 # ifdef _MSC_VER
85 /* Set it in the SSE unit. */
86 unsigned int mxcsr, orig_mxcsr;
87 _FPU_GETSSECW (orig_mxcsr);
88 mxcsr = (orig_mxcsr & ~(0x0C00 << 3)) | (rounding_direction << 3);
89 if (mxcsr != orig_mxcsr)
90 _FPU_SETSSECW (mxcsr);
91 # else
92 /* Set it in the 387 unit. */
93 unsigned short fctrl, orig_fctrl;
94 _FPU_GETCW (orig_fctrl);
95 fctrl = (orig_fctrl & ~0x0C00) | rounding_direction;
96 if (fctrl != orig_fctrl)
97 _FPU_SETCW (fctrl);
99 if (CPU_HAS_SSE ())
101 /* Set it in the SSE unit as well. */
102 unsigned int mxcsr, orig_mxcsr;
103 _FPU_GETSSECW (orig_mxcsr);
104 mxcsr = (orig_mxcsr & ~(0x0C00 << 3)) | (rounding_direction << 3);
105 if (mxcsr != orig_mxcsr)
106 _FPU_SETSSECW (mxcsr);
108 # endif
110 return 0;
113 # elif defined __aarch64__ /* arm64 */
116 fegetround (void)
118 unsigned long fpcr;
119 _FPU_GETCW (fpcr);
120 return (fpcr & 0x00C00000UL)
121 # if FE_TOWARDZERO == 3 /* FreeBSD compatible FE_* values */
122 >> 22
123 # endif
128 fesetround (int rounding_direction)
130 # if FE_TOWARDZERO == 3 /* FreeBSD compatible FE_* values */
131 if ((rounding_direction & ~3) != 0)
132 return -1;
133 rounding_direction = rounding_direction << 22;
134 # else /* glibc compatible FE_* values */
135 if ((rounding_direction & ~0x00C00000UL) != 0)
136 return -1;
137 # endif
138 unsigned long fpcr, orig_fpcr;
139 _FPU_GETCW (orig_fpcr);
140 fpcr = (orig_fpcr & ~0x00C00000UL) | rounding_direction;
141 if (fpcr != orig_fpcr)
142 _FPU_SETCW (fpcr);
143 return 0;
146 # elif defined __arm__
149 fegetround (void)
151 # ifdef __SOFTFP__
152 return FE_TONEAREST;
153 # else
154 unsigned int fpscr;
155 _FPU_GETCW (fpscr);
156 return fpscr & 0x00C00000U;
157 # endif
161 fesetround (int rounding_direction)
163 # ifdef __SOFTFP__
164 if (rounding_direction != FE_TONEAREST)
165 return -1;
166 # else
167 if ((rounding_direction & ~0x00C00000U) != 0)
168 return -1;
169 unsigned int fpscr, orig_fpscr;
170 _FPU_GETCW (orig_fpscr);
171 fpscr = (orig_fpscr & ~0x00C00000U) | rounding_direction;
172 if (fpscr != orig_fpscr)
173 _FPU_SETCW (fpscr);
174 # endif
175 return 0;
178 # elif defined __alpha
181 fegetround (void)
183 unsigned long fpcr;
184 _FPU_GETCW (fpcr);
185 return (fpcr >> 58) & 0x3UL;
189 fesetround (int rounding_direction)
191 if ((rounding_direction & ~0x3UL) != 0)
192 return -1;
193 unsigned long fpcr, orig_fpcr;
194 _FPU_GETCW (orig_fpcr);
195 fpcr = (orig_fpcr & ~(0x3UL << 58))
196 | ((unsigned long) rounding_direction << 58);
197 if (fpcr != orig_fpcr)
198 _FPU_SETCW (fpcr);
199 return 0;
202 # elif defined __hppa
205 fegetround (void)
207 unsigned int fpstatus;
208 _FPU_GETCW (fpstatus);
209 return fpstatus & 0x00000600U;
213 fesetround (int rounding_direction)
215 if ((rounding_direction & ~0x00000600U) != 0)
216 return -1;
217 unsigned int fpstatus, orig_fpstatus;
218 _FPU_GETCW (orig_fpstatus);
219 fpstatus = (orig_fpstatus & ~0x00000600U) | rounding_direction;
220 if (fpstatus != orig_fpstatus)
221 _FPU_SETCW (fpstatus);
222 return 0;
225 # elif defined __ia64__
228 fegetround (void)
230 unsigned long fpsr;
231 _FPU_GETCW (fpsr);
232 return (fpsr >> 10) & 0x3UL;
236 fesetround (int rounding_direction)
238 if ((rounding_direction & ~0x3UL) != 0)
239 return -1;
240 unsigned long fpsr, orig_fpsr;
241 _FPU_GETCW (orig_fpsr);
242 fpsr = (orig_fpsr & ~(0x3UL << 10))
243 | ((unsigned long) rounding_direction << 10);
244 if (fpsr != orig_fpsr)
245 _FPU_SETCW (fpsr);
246 return 0;
249 # elif defined __m68k__
252 fegetround (void)
254 unsigned int fpcr;
255 _FPU_GETCW (fpcr);
256 return fpcr & 0x30U;
260 fesetround (int rounding_direction)
262 if ((rounding_direction & ~0x30U) != 0)
263 return -1;
264 unsigned int fpcr, orig_fpcr;
265 _FPU_GETCW (orig_fpcr);
266 fpcr = (orig_fpcr & ~0x30U) | rounding_direction;
267 if (fpcr != orig_fpcr)
268 _FPU_SETCW (fpcr);
269 return 0;
272 # elif defined __mips__
275 fegetround (void)
277 unsigned int fcsr;
278 _FPU_GETCW (fcsr);
279 return fcsr & 0x3U;
283 fesetround (int rounding_direction)
285 if ((rounding_direction & ~0x3U) != 0)
286 return -1;
287 unsigned int fcsr, orig_fcsr;
288 _FPU_GETCW (orig_fcsr);
289 fcsr = (orig_fcsr & ~0x3U) | rounding_direction;
290 if (fcsr != orig_fcsr)
291 _FPU_SETCW (fcsr);
292 return 0;
295 # elif defined __loongarch__
298 fegetround (void)
300 unsigned int fcsr;
301 _FPU_GETCW (fcsr);
302 return fcsr & 0x300U;
306 fesetround (int rounding_direction)
308 if ((rounding_direction & ~0x300U) != 0)
309 return -1;
310 unsigned int fcsr, orig_fcsr;
311 _FPU_GETCW (orig_fcsr);
312 fcsr = (orig_fcsr & ~0x300U) | rounding_direction;
313 if (fcsr != orig_fcsr)
314 _FPU_SETCW (fcsr);
315 return 0;
318 # elif defined __powerpc__
320 /* The AIX header files have different values for the rounding directions
321 than all the other platforms: The values 0 and 1 are swapped.
322 (They probably did this in order to have a trivial FLT_ROUNDS macro, cf.
323 <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/float.h.html>.)
324 Define some handy macros for conversion. */
325 # ifdef _AIX
326 # define fe_to_hardware(x) ((x) ^ ((x) < 2))
327 # define hardware_to_fe(x) ((x) ^ ((x) < 2))
328 # else
329 # define fe_to_hardware(x) (x)
330 # define hardware_to_fe(x) (x)
331 # endif
334 fegetround (void)
336 # if 1
337 unsigned int result;
338 __asm__ __volatile__ ("mcrfs 7,7 ; mfcr %0" : "=r" (result) : : "cr7");
339 return hardware_to_fe (result & 3);
340 # else
341 union { unsigned long long u; double f; } memenv;
342 _FPU_GETCW_AS_DOUBLE (memenv.f);
343 return hardware_to_fe (memenv.u & 3);
344 # endif
348 fesetround (int rounding_direction)
350 if ((rounding_direction & ~0x3U) != 0)
351 return -1;
352 # ifdef _AIX
353 rounding_direction = fe_to_hardware (rounding_direction);
354 # endif
355 if (rounding_direction & 2)
356 __asm__ __volatile__ ("mtfsb1 30");
357 else
358 __asm__ __volatile__ ("mtfsb0 30");
359 if (rounding_direction & 1)
360 __asm__ __volatile__ ("mtfsb1 31");
361 else
362 __asm__ __volatile__ ("mtfsb0 31");
363 return 0;
366 # elif defined __riscv
369 fegetround (void)
371 int rounding_direction;
372 __asm__ __volatile__ ("frrm %0" : "=r" (rounding_direction));
373 # if FE_UPWARD == 0x60 /* FreeBSD compatible FE_* values */
374 return rounding_direction << 5;
375 # else
376 return rounding_direction;
377 # endif
381 fesetround (int rounding_direction)
383 # if FE_UPWARD == 0x60 /* FreeBSD compatible FE_* values */
384 if ((rounding_direction & ~0x60) != 0)
385 return -1;
386 rounding_direction = rounding_direction >> 5;
387 # else
388 if ((rounding_direction & ~3) != 0)
389 return -1;
390 # endif
391 __asm__ __volatile__ ("fsrm %z0" : : "rJ" (rounding_direction));
392 return 0;
395 # elif defined __s390__ || defined __s390x__
398 fegetround (void)
400 unsigned int fpc;
401 _FPU_GETCW (fpc);
402 return fpc & 0x3U;
406 fesetround (int rounding_direction)
408 if ((rounding_direction & ~0x3U) != 0)
409 return -1;
410 # if 1
411 __asm__ __volatile__ ("srnm 0(%0)" : : "a" (rounding_direction));
412 # else
413 unsigned int fpc, orig_fpc;
414 _FPU_GETCW (orig_fpc);
415 fpc = (orig_fpc & ~0x3U) | rounding_direction;
416 if (fpc != orig_fpc)
417 _FPU_SETCW (fpc);
418 # endif
419 return 0;
422 # elif defined __sh__
425 fegetround (void)
427 unsigned int fpscr;
428 _FPU_GETCW (fpscr);
429 return fpscr & 0x1U;
433 fesetround (int rounding_direction)
435 if ((rounding_direction & ~0x1U) != 0)
436 return -1;
437 unsigned int fpscr, orig_fpscr;
438 _FPU_GETCW (orig_fpscr);
439 fpscr = (orig_fpscr & ~0x1U) | rounding_direction;
440 if (fpscr != orig_fpscr)
441 _FPU_SETCW (fpscr);
442 return 0;
445 # elif defined __sparc
448 fegetround (void)
450 unsigned long fsr;
451 _FPU_GETCW (fsr);
452 return (fsr & 0xC0000000UL)
453 # if FE_DOWNWARD == 3 /* FreeBSD compatible FE_* values */
454 >> 30
455 # endif
460 fesetround (int rounding_direction)
462 # if FE_DOWNWARD == 3 /* FreeBSD compatible FE_* values */
463 if ((rounding_direction & ~3) != 0)
464 return -1;
465 rounding_direction = (unsigned int) rounding_direction << 30;
466 # else /* glibc compatible FE_* values */
467 if (((unsigned int) rounding_direction & ~0xC0000000UL) != 0)
468 return -1;
469 # endif
470 unsigned long fsr, orig_fsr;
471 _FPU_GETCW (orig_fsr);
472 fsr = (orig_fsr & ~0xC0000000UL) | (unsigned int) rounding_direction;
473 if (fsr != orig_fsr)
474 _FPU_SETCW (fsr);
475 return 0;
478 # else
480 # if defined __GNUC__ || defined __clang__
481 # warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>."
482 # endif
483 # define NEED_FALLBACK 1
485 # endif
487 #else
489 /* The compiler does not support __asm__ statements or equivalent
490 intrinsics. */
492 # if HAVE_FPSETROUND
493 /* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */
495 /* Get fpgetround, fpsetround. */
496 # include <ieeefp.h>
499 fegetround (void)
501 return fpgetround ();
505 fesetround (int rounding_direction)
507 fpsetround (rounding_direction);
508 return 0;
511 # elif defined _AIX /* AIX */
513 /* Get fp_read_rnd, fp_swap_rnd. */
514 # include <float.h>
516 /* Documentation:
517 <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-read-rnd-fp-swap-rnd-subroutine> */
520 fegetround (void)
522 return fp_read_rnd ();
526 fesetround (int rounding_direction)
528 fp_swap_rnd (rounding_direction);
529 return 0;
532 # else
534 # define NEED_FALLBACK 1
536 # endif
538 #endif
540 #if NEED_FALLBACK
542 /* A dummy fallback. */
544 # include <float.h>
545 # include <stdlib.h>
548 fegetround (void)
550 /* Cf. <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/float.h.html> */
551 switch (FLT_ROUNDS)
553 case 0: return FE_TOWARDZERO;
554 case 1: return FE_TONEAREST;
555 case 2: return FE_UPWARD;
556 case 3: return FE_DOWNWARD;
557 default: abort ();
562 fesetround (int rounding_direction)
564 if (rounding_direction != fegetround ())
565 return -1;
566 return 0;
569 #endif