【问题标题】:Accounting for rounding mode in a constexpr function在 constexpr 函数中考虑舍入模式
【发布时间】:2018-11-12 14:50:07
【问题描述】:

我正在尝试编写constexpr 版本的exp 函数。我知道

  • 我使用的算法是为FE_TONEAREST 舍入设计的
  • 从C++11开始,可以通过fesetround( int round )更改舍入模式(前提是支持#pragma STDC FENV_ACCESS并设置为ON
  • 我不允许在我自己的 constexpr 函数中调用非 constexpr 函数 fesetround(int)

如果我的理解是正确的,这意味着(在支持#pragma STDC FENV_ACCESS 的编译器中),用户可以在调用我的函数之前设置舍入模式,但不允许我的函数取消此操作更改(即使是暂时的),因此可能会以错误的舍入模式执行。

我能想到的最佳选择是有两个功能:

  • 版本 A 标记为 constexpr 且未设置舍入模式
  • 版本 B 没有标记为 constexpr 并做了 3 件事:
    1. 将舍入模式设置为FE_TONEAREST
    2. 调用版本 A
    3. 重置舍入模式
    4. 返回版本A的结果

要设置constexpr 变量,必须调用版本A,但它总是会被评估为好像舍入模式是FE_TONEAREST,因为(根据cppreference):

当前的舍入模式不影响 .... 结果 常量表达式中的浮点算术运算符(总是 最近的)

在非 constexpr contexts 中,只要舍入模式为 FE_TONEAREST,版本 A 和版本 B 会一致,但版本 B 将为任何其他舍入模式提供更好的结果。用户有责任使用FE_TONEAREST 舍入确保调用版本 B。

在标准 C++ 中是否有更好的方法来处理这个问题? ...或者标准是否有其他关于舍入模式的内容,这使得整个问题变得毫无意义?

编辑:我的目标不是尊重舍入模式;是为了忽略它,以便函数的准确性是一致的。我宁愿只使用版本 B,但无法在 constexpr 函数中更改舍入模式。

【问题讨论】:

  • 所以调用版本 A(如果不是 constexpr)的结果可能/应该根据舍入模式而有所不同?
  • 是的;该算法执行浮点加法/乘法,因此在非constexpr 上下文中,我希望它尊重舍入模式
  • 所以您希望编译时间常数在运行时具有不同的值?它不能。您可以在运行时选择不同的constexpr 值,也可以在运行时计算(使用当前舍入模式)。
  • @user1476176:由于英语的性质,您所说的“我希望它尊重舍入模式”是模棱两可的。目前尚不清楚您的意思是您期望值会有所不同,因为舍入模式实际上会影响操作,或者您的意思是您希望结果取决于舍入模式.
  • @1201ProgramAlarm:见我上面的评论。

标签: c++ floating-point rounding


【解决方案1】:

C 和 C++ 提供的 FENV_ACCESS 功能是一个杂项,不支持在此问题中寻求的用途。修改舍入模式的程序很少见,对它们的语言支持很差。

根据 C 标准(并由 C++ 通过引用继承),FENV_ACCESS 通知程序可能在非默认浮点控制模式下运行的实现。如果您允许在非默认模式下调用您的 exp 函数并且未在 FENV_ACCESS 开启时进行转换,则标准不会告诉我们行为将是什么。

此外,C++ 没有提供任何方法来自动调用不同版本的例程,具体取决于舍入模式或FENV_ACCESS 的状态(尽管 C++ 实现可能会作为语言的扩展这样做)。

您从cppreference 中引用的关于舍入模式不影响常量表达式中的算术运算的声明似乎是由 cppreference 得出的结论,而不是 C++ 标准中明确规定的内容。也许这是因为常量表达式是在翻译时计算的,所以不能对浮点环境进行修改。但是,我不清楚标准是否完全保证了这一点——expr.const 部分中的一条注释说“常量表达式可以在翻译过程中求值”。它并没有说必须在翻译过程中对其进行评估。

一般来说,当舍入模式接近于接近无穷大或其他设置时,人们不希望exp 的行为方式相同。相反,人们会希望exp 在众数趋向无穷时返回向上舍入的结果,当众数趋向负无穷时返回舍入结果,而当众数趋向于零时返回舍入结果。 (获得这些结果需要对每种模式使用不同的exp 实现,因为仅对它所做的每个操作应用舍入不会产生所需的结果。)因此,无论舍入模式如何,您要求exp 返回相同的结果是有点不寻常。当程序使用不同的舍入模式评估浮点运算时,返回一个舍入到最接近的结果的目的是什么?如果程序正在使用朝向无穷大来尝试计算上限,则最近的exp 会破坏该计算。

【讨论】:

  • “如果你允许你的 exp 函数在非默认模式下被调用,并且它没有在 FENV_ACCESS 开启的情况下被翻译,标准不​​会告诉我们行为会是什么。”有没有办法禁止使用非默认模式调用我的函数(如果舍入模式不是最近的则中止)?另外,请记住,这个函数是constexpr,因此是inline——它总是与客户端代码一起翻译,即,与客户端代码使用的FENV_ACCESS状态相同。
  • (2) “C++ 没有提供任何方法来根据舍入模式自动调用不同版本的例程” - switch(fegetround()) { case FE_DOWNWARD: ... case FE_UPWARD: .... } 怎么样?这不能在 constexpr 函数中完成,但除此之外它可以工作,不是吗?
  • (3) “无论舍入模式如何,您要求 exp 返回相同结果的请求有点不寻常。” - glibc 的实现就是这样做的。它将舍入模式设置为最近,执行中间计算,然后重置舍入模式并返回 (code.woboq.org/userspace/glibc/sysdeps/ieee754/dbl-64/…)。我也会这样做,但我无法在 constexpr 函数中更改舍入模式。我的目标不是尊重舍入模式;这是为了忽略它,以便函数的准确性是一致的。
  • Cppreference 取自 C 标准 F.8.2。公平地说,它被简化了(在 C 中,表达式必须是静态或线程本地初始化程序的一部分)
  • @Cubbi: C F.8.2 说在翻译过程中取整到最近有效。这与说constexpr 使用四舍五入是不同的。 C++ 标准在注释中说,常量表达式可以在翻译期间评估,但我没有看到它说它们在翻译期间被评估。因此,没有明确声明常量表达式是用四舍五入计算的。这是cppreference得出的结论。
猜你喜欢
  • 2023-03-20
  • 2023-03-03
  • 2019-07-26
  • 1970-01-01
  • 2023-02-12
  • 2020-11-02
  • 1970-01-01
  • 2019-09-28
  • 1970-01-01
相关资源
最近更新 更多