在c++中使用不同的IEEE浮点舍入模式

Working with different IEEE floating-point rounding modes in C++

本文关键字:IEEE 舍入 模式 c++      更新时间:2023-10-16

我真倒霉,我必须确保在GPU和CPU上获得相同的浮点结果。好的,我知道IEEE已经照顾了我,并提供了一个很好的标准,坚持几个四舍五入选项;CUDA部分被整理出来(有不同舍入模式的内在特性),所以这只是动机。

但是在主机端c++代码中-我如何在特定的舍入模式下执行浮点运算(我的意思是在特定的语句中,而不是在整个翻译单元中)?是否有包装器函数在底层使用汇编?是否有一组具有不同舍入模式的浮点数代理的类?

我也问同样的问题关于翻译单位水平。如何使编译器(gcc/clang/MSVC)在编译翻译单元时默认为某种舍入模式?

在@AndreasPapadopoulos的引导下,看起来有一种官方的方式来改变舍入模式:

int fesetround(int round)
int fegetround()

但是有几个注意事项:

  1. 这是c++ 11,而不是c++ 98(尽管在实践中你可能只使用你的系统的<fenv.h>是C99)。
  2. 需要通过#pragma
  3. 与编译器通信。

我不确定这在实践中使用得有多广泛(以及是否有更常用的更好的抽象)。

处理这个问题的一个简单方法是引入一个类,它在初始化时设置舍入模式,并在超出作用域时将其重置为前一个模式,如下所示:

#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";
    }
  }
};

你可以这样使用

void foo(){
  const auto srm = SetRoundingMode(FE_UPWARD);
}

请注意,所有舍入模式都在类中列出。