Working with different IEEE floating-point rounding modes in C++
An answer to this question on Stack Overflow.
Question
Woe is me, I have to ensure the same floating-point results on a GPU and on the CPU. Ok, I understand IEEE has taken care of me and provided a nice standard to adhere to with several rounding options; and the CUDA part is sorted out (there are intrinsics for the different rounding modes), so that's just motivation.
But in host-side C++ code - how do I perform floating-point arithmetic in a specific rounding mode (and I mean in a specific statement, not throughout my translation unit)? Are there wrapper functions which use assembly under the hood? Is there a set of classes for floating point number proxies with the different rounding modes?
I'm also asking the same question about the translation-unit level. How do I make the compiler (gcc/clang/MSVC) default to a certain rounding mode when compiling a translation unit?
Answer
One simple way of handling this is to introduce a class that sets the rounding mode on its initialization and resets it to the previous mode when it goes out of scope, like so:
#include <cfenv>
//Simple class to enable directed rounding in floating-point math and to reset
//the rounding mode afterwards, when it goes out of scope
struct SetRoundingMode {
const int old_rounding_mode;
SetRoundingMode(const int mode) : old_rounding_mode(fegetround()) {
if(std::fesetround(mode)!=0){
throw std::runtime_error("Failed to set directed rounding mode!");
}
}
~SetRoundingMode(){
// More recent versions of C++ don't like exceptions in the destructor
// so you may need a different way of handling issues here.
if(std::fesetround(old_rounding_mode)!=0){
throw std::runtime_error("Failed to reset rounding mode to original value!");
}
}
static std::string get_rounding_mode_string() {
switch (fegetround()) {
case FE_DOWNWARD: return "downward";
case FE_TONEAREST: return "to-nearest";
case FE_TOWARDZERO: return "toward-zero";
case FE_UPWARD: return "upward";
default: return "unknown";
}
}
};
You can then use this like
void foo(){
const auto srm = SetRoundingMode(FE_UPWARD);
}
Note that all the rounding modes are listed within the class.