1 /* Functions for controlling the floating-point rounding direction.
2 Copyright (C) 1997-2025 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}. */
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)
35 /* Use the rounding direction from the SSE unit. */
37 _FPU_GETSSECW (mxcsr
);
38 unsigned int fctrl
= (mxcsr
>> 3) & 0x0C00;
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. */
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
51 0x0000 -> FE_TONEAREST = 0
54 0x0C00 -> FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD */
55 return (fctrl
& 0x0800 ? FE_UPWARD
: 0) | (fctrl
& 0x0400 ? FE_DOWNWARD
: 0);
57 return fctrl
& 0x0C00;
62 fesetround (int rounding_direction
)
65 /* The MSVC header files have different values for the rounding directions
66 than all the other platforms. */
67 if ((rounding_direction
& ~0x0300) != 0)
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
72 FE_TONEAREST = 0 -> 0x0000
75 FE_TOWARDZERO = FE_DOWNWARD | FE_UPWARD -> 0x0C00 */
77 (rounding_direction
& FE_UPWARD
? 0x0800 : 0)
78 | (rounding_direction
& FE_DOWNWARD
? 0x0400 : 0);
80 if ((rounding_direction
& ~0x0C00) != 0)
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
);
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
)
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
);
113 # elif defined __aarch64__ /* arm64 */
120 return (fpcr
& 0x00C00000UL
)
121 # if FE_TOWARDZERO == 3 /* FreeBSD compatible FE_* values */
128 fesetround (int rounding_direction
)
130 # if FE_TOWARDZERO == 3 /* FreeBSD compatible FE_* values */
131 if ((rounding_direction
& ~3) != 0)
133 rounding_direction
= rounding_direction
<< 22;
134 # else /* glibc compatible FE_* values */
135 if ((rounding_direction
& ~0x00C00000UL
) != 0)
138 unsigned long fpcr
, orig_fpcr
;
139 _FPU_GETCW (orig_fpcr
);
140 fpcr
= (orig_fpcr
& ~0x00C00000UL
) | rounding_direction
;
141 if (fpcr
!= orig_fpcr
)
146 # elif defined __arm__
156 return fpscr
& 0x00C00000U
;
161 fesetround (int rounding_direction
)
164 if (rounding_direction
!= FE_TONEAREST
)
167 if ((rounding_direction
& ~0x00C00000U
) != 0)
169 unsigned int fpscr
, orig_fpscr
;
170 _FPU_GETCW (orig_fpscr
);
171 fpscr
= (orig_fpscr
& ~0x00C00000U
) | rounding_direction
;
172 if (fpscr
!= orig_fpscr
)
178 # elif defined __alpha
185 return (fpcr
>> 58) & 0x3UL
;
189 fesetround (int rounding_direction
)
191 if ((rounding_direction
& ~0x3UL
) != 0)
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
)
202 # elif defined __hppa
207 unsigned int fpstatus
;
208 _FPU_GETCW (fpstatus
);
209 return fpstatus
& 0x00000600U
;
213 fesetround (int rounding_direction
)
215 if ((rounding_direction
& ~0x00000600U
) != 0)
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
);
225 # elif defined __ia64__
232 return (fpsr
>> 10) & 0x3UL
;
236 fesetround (int rounding_direction
)
238 if ((rounding_direction
& ~0x3UL
) != 0)
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
)
249 # elif defined __m68k__
260 fesetround (int rounding_direction
)
262 if ((rounding_direction
& ~0x30U
) != 0)
264 unsigned int fpcr
, orig_fpcr
;
265 _FPU_GETCW (orig_fpcr
);
266 fpcr
= (orig_fpcr
& ~0x30U
) | rounding_direction
;
267 if (fpcr
!= orig_fpcr
)
272 # elif defined __mips__
283 fesetround (int rounding_direction
)
285 if ((rounding_direction
& ~0x3U
) != 0)
287 unsigned int fcsr
, orig_fcsr
;
288 _FPU_GETCW (orig_fcsr
);
289 fcsr
= (orig_fcsr
& ~0x3U
) | rounding_direction
;
290 if (fcsr
!= orig_fcsr
)
295 # elif defined __loongarch__
302 return fcsr
& 0x300U
;
306 fesetround (int rounding_direction
)
308 if ((rounding_direction
& ~0x300U
) != 0)
310 unsigned int fcsr
, orig_fcsr
;
311 _FPU_GETCW (orig_fcsr
);
312 fcsr
= (orig_fcsr
& ~0x300U
) | rounding_direction
;
313 if (fcsr
!= orig_fcsr
)
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. */
326 # define fe_to_hardware(x) ((x) ^ ((x) < 2))
327 # define hardware_to_fe(x) ((x) ^ ((x) < 2))
329 # define fe_to_hardware(x) (x)
330 # define hardware_to_fe(x) (x)
338 __asm__
__volatile__ ("mcrfs 7,7 ; mfcr %0" : "=r" (result
) : : "cr7");
339 return hardware_to_fe (result
& 3);
341 union { unsigned long long u
; double f
; } memenv
;
342 _FPU_GETCW_AS_DOUBLE (memenv
.f
);
343 return hardware_to_fe (memenv
.u
& 3);
348 fesetround (int rounding_direction
)
350 if ((rounding_direction
& ~0x3U
) != 0)
353 rounding_direction
= fe_to_hardware (rounding_direction
);
355 if (rounding_direction
& 2)
356 __asm__
__volatile__ ("mtfsb1 30");
358 __asm__
__volatile__ ("mtfsb0 30");
359 if (rounding_direction
& 1)
360 __asm__
__volatile__ ("mtfsb1 31");
362 __asm__
__volatile__ ("mtfsb0 31");
366 # elif defined __riscv
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;
376 return rounding_direction
;
381 fesetround (int rounding_direction
)
383 # if FE_UPWARD == 0x60 /* FreeBSD compatible FE_* values */
384 if ((rounding_direction
& ~0x60) != 0)
386 rounding_direction
= rounding_direction
>> 5;
388 if ((rounding_direction
& ~3) != 0)
391 __asm__
__volatile__ ("fsrm %z0" : : "rJ" (rounding_direction
));
395 # elif defined __s390__ || defined __s390x__
406 fesetround (int rounding_direction
)
408 if ((rounding_direction
& ~0x3U
) != 0)
411 __asm__
__volatile__ ("srnm 0(%0)" : : "a" (rounding_direction
));
413 unsigned int fpc
, orig_fpc
;
414 _FPU_GETCW (orig_fpc
);
415 fpc
= (orig_fpc
& ~0x3U
) | rounding_direction
;
422 # elif defined __sh__
433 fesetround (int rounding_direction
)
435 if ((rounding_direction
& ~0x1U
) != 0)
437 unsigned int fpscr
, orig_fpscr
;
438 _FPU_GETCW (orig_fpscr
);
439 fpscr
= (orig_fpscr
& ~0x1U
) | rounding_direction
;
440 if (fpscr
!= orig_fpscr
)
445 # elif defined __sparc
452 return (fsr
& 0xC0000000UL
)
453 # if FE_DOWNWARD == 3 /* FreeBSD compatible FE_* values */
460 fesetround (int rounding_direction
)
462 # if FE_DOWNWARD == 3 /* FreeBSD compatible FE_* values */
463 if ((rounding_direction
& ~3) != 0)
465 rounding_direction
= (unsigned int) rounding_direction
<< 30;
466 # else /* glibc compatible FE_* values */
467 if (((unsigned int) rounding_direction
& ~0xC0000000UL
) != 0)
470 unsigned long fsr
, orig_fsr
;
471 _FPU_GETCW (orig_fsr
);
472 fsr
= (orig_fsr
& ~0xC0000000UL
) | (unsigned int) rounding_direction
;
480 # if defined __GNUC__ || defined __clang__
481 # warning "Unknown CPU / architecture. Please report your platform and compiler to <bug-gnulib@gnu.org>."
483 # define NEED_FALLBACK 1
489 /* The compiler does not support __asm__ statements or equivalent
493 /* FreeBSD ≥ 3.1, NetBSD ≥ 1.1, OpenBSD, IRIX, Solaris, Minix ≥ 3.2. */
495 /* Get fpgetround, fpsetround. */
501 return fpgetround ();
505 fesetround (int rounding_direction
)
507 fpsetround (rounding_direction
);
511 # elif defined _AIX /* AIX */
513 /* Get fp_read_rnd, fp_swap_rnd. */
517 <https://www.ibm.com/docs/en/aix/7.3?topic=f-fp-read-rnd-fp-swap-rnd-subroutine> */
522 return fp_read_rnd ();
526 fesetround (int rounding_direction
)
528 fp_swap_rnd (rounding_direction
);
534 # define NEED_FALLBACK 1
542 /* A dummy fallback. */
550 /* Cf. <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/float.h.html> */
553 case 0: return FE_TOWARDZERO
;
554 case 1: return FE_TONEAREST
;
555 case 2: return FE_UPWARD
;
556 case 3: return FE_DOWNWARD
;
562 fesetround (int rounding_direction
)
564 if (rounding_direction
!= fegetround ())