Skip to content

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.