I want to use SVE instructions to round FP32 to FP16. ARM docs say that the SVE instructions will round according to the current rounding mode. So, I need to set the rounding mode, since I don't know what code ran before mine.
Edit: to clarify, I just want the default mode, RTNE.
The C++ docs for fenv say:
The floating-point environment access and modification is only meaningful when #pragma STDC FENV_ACCESS is supported and is set to ON.
But, if I look at another page on cppreference:
The ISO C++ language standard does not require the compilers to support any pragmas.
OK, so I need use a pragma, but the standard doesn't require compilers to support any pragmas. Surely, at least clang supports it, right?
error: ignoring ‘#pragma STDC FENV_ACCESS’ [-Werror=unknown-pragmas]
Also, cppreference says that if I suppress that error and enable the FENV_ACCESS pragma, it will make all of my code slower:
optimizations that could subvert flag tests and mode changes (e.g., global common subexpression elimination, code motion, and constant folding) are prohibited.
On the other hand, I need to modify the floating-point environment. I need my rounding to work correctly.
What am I supposed to do here? Currently, I am just not setting the pragma and hoping for the best, but what is the correct solution?
Peculiar indeed. If you really have to set rounding mode, the only truly supported option seems to be moving all the floating point math to a C library that makes sure to reset the rounding mode after itself.
First, what you have found is true - C++ standard intends this functionality to be exactly as in C, but at the same time explicitly says they do not require support of #pragma FENV_ACCESS in the compiler. Everything is left implementation-defined. See this point from standard:
The contents and meaning of the header are the same as the C standard library header <fenv.h>. [Note 1: This document does not require an implementation to support the FENV_ACCESS pragma; it is implementation-defined ([cpp.pragma]) whether the pragma is supported. As a consequence, it is implementation-defined whether these functions can be used to test floating-point status flags, set floating-point control modes, or run under non-default mode settings. If the pragma is used to enable control over the floating-point environment, this document does not specify the effect on floating-point evaluation in constant expressions. — end note]
What you can do (I think) is check the default round style with std::numeric_limits<double>::round_style. Pretty much the best you can do in standard C++ is check what your compiler returns for given architecture (hoping that it's not std::round_indeterminate), add assert to verify it didn't change with compiler upgrade
static_assert(std::numeric_limits<double>::round_style == std::round_to_nearest);
and hope it never fires.
clang does not state support for C++ explicitly, but given it only mentions C when talking about these pragmas, I think it's safe bet that it's only supported in C. From clang 19.1.0 documentation:
[...] C and C++ restrict access to the floating point environment by default, and the compiler is allowed to assume that all operations are performed in the default environment. [...] C provides two pragmas to allow code to dynamically modify the floating point environment:
#pragma STDC FENV_ACCESS ONallows dynamic changes to the entire floating point environment.#pragma STDC FENV_ROUND FE_DYNAMICallows dynamic changes to just the floating point rounding mode. This may be more optimizable than FENV_ACCESS ON because the compiler can still ignore the possibility of floating-point exceptions by default.[...] See the C standard for more information about these pragmas.
There are compiler flags that control floating point math behaviour, including simulating these pragmas, but this is one giant booby trap filled with Undefined Behaviour (to be fair, pragmas are as well). You should read all of the documentation for floating point options before tampering with them. Doc for clang 19.1.0 can be found here.
One more thing about what you wrote:
So, I need to set the rounding mode, since I don't know what code ran before mine.
If it's a different program, it shouldn't matter. If within the same program one calls a function with different pragma/flag set, you are already in Undefined Behaviour land. So hopefully all the libraries you use clean up after themselves if they ever try to modify it.
Your question does not state which rounding method you wish to use. If you wish to use round-to-nearest, you “should be” able to simply assume it is in effect, because round-to-nearest is the default mode, and code compiled without FENV_ACCESS on may assume the rounding mode1 is the default. “Should be” is in quotes to indicate this is the normal and most common practice, although it is not mandated by language standards. The remainder of this answer explains why.
As you note, the C++ standard does not require support for FENV_ACCESS. Further, the C++ standard inherits some specification of floating-point behaviors from the C standard, including the <fenv.h> features. I will cite statements from the current C standard, which is too new to have been adopted by C++, but these are substantially the same for prior versions of the C standard.
The default mode is round-to-nearest-ties-to-even. This is set upon program startup, per C 2024 F.8.4:
At program startup… The dynamic rounding direction mode is rounding to nearest.
Annex F is optional. However, round-to-nearest is overwhelmingly the most common default. Since it is not mandated by the standards, it would be up to your C++ implementation (which includes the operating system specification, not just the compiler) to specify what the default mode is. Round-to-nearest is so overwhelmingly used that it is likely merely assumed by the compiler, the standard library, and the operating system without explicitly stating it.
Regarding the default state of FENV_ACCESS; C 2024 7.6.2 says:
… The default state ("on" or "off") for the pragma is implementation-defined…
and Clang says its default is off for floating-point models other than strict (command-line switch -ffp-model=strict).
If you do wish to compile with the strict model, you can turn FENV_ACCESS off at the start of your translation unit with #pragma STDC FENV_ACCESS OFF. Although you report Clang complains when #pragma STDC FENV_ACCESS ON is used, testing with Compiler Explorer shows Clang does not complain about setting it to OFF rather than ON at least as far back as Clang 3.0. Further, support for turning it on appears to have been added in Clang 12.0. The current version is 19.1.0, so you are using quite an old version.
When FENV_ACCESS is off, code may assume the default rounding mode is in effect, because C 7.6.2 says:
… If part of a program… is executed with non-default floating-point mode settings using any means other than the
FENV_ROUNDpragmas, but was translated with the state for theFENV_ACCESSpragma "off", the behavior is undefined…
The import of that is that any other code that changes the rounding mode and calls your routine, directly or indirectly, is responsible for ensuring that the rounding mode is set to the default before your routine is called.
Thus, you may ensure FENV_ACCESS is off by using a floating-point model other than strict or by using #pragma STDC FENV_ACCESS OFF in your code. This conclusion is not based upon strict requirements of the C++ standard but rather ubiquitous practice plus specific assertions of the Clang documentation.
1 The IEEE-754 standard does not require a rounding mode; it has no requirement that a computing environment maintain any global state controlling what rounding method is used. Per the 754 standard, operations that require rounding are performed using some rounding method, but the standard is agnostic as to how that method is specified. It can be individually specified with each operation, can be specified with some unit of code, or can be a global mode. C and C++ use a global mode model.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With