标准对 std::pow、std::log 等 cmath 函数有啥看法?

Posted

技术标签:

【中文标题】标准对 std::pow、std::log 等 cmath 函数有啥看法?【英文标题】:What does standard say about cmath functions like std::pow, std::log etc?标准对 std::pow、std::log 等 cmath 函数有什么看法? 【发布时间】:2015-10-18 18:17:47 【问题描述】:

标准是否保证函数在所有实现中返回完全相同的结果?

pow(float,float) 为例,用于 32 位 IEEE 浮点数。如果传入相同的两个浮点数,所有实现的结果是否相同?

或者根据用于实现pow 的算法,标准是否允许一些关于微小差异的灵活性?

【问题讨论】:

标准不强制 floatdouble 为 IEEE-754 类型。它们可以是特定于机器的类型,具有不同的范围和精度。 如果您有两台机器对floats 和doubles 使用相同的表示,并且在舍入方面具有相同的设置,我不知道答案。在所有其他情况下,答案是否定的,它们会给出不同的结果。 在遵循 ieee754 的同一台机器上还有两个不同的编译器。如果 std 说是的答案应该是相同的,那么它必须定义正确的答案是什么。 相关:Does any floating point-intensive code produce bit-exact results in any x86-based architecture?:摘要:C 中没有,但 asm 中是。 【参考方案1】:

不,C++ 标准不要求 cmath 函数的结果在所有实现中都相同。对于初学者,您可能无法获得 IEEE-754/IEC 60559 浮点算法。

也就是说,如果实现确实使用 IEC 60559 并定义 __STDC_IEC_559__,那么它必须遵守 C 标准的附件 F(是的,您的问题是关于 C++,但 C++ 标准遵循 C 标头的 C 标准,例如 math.h)。附件 F 指出:

float 类型与 IEC 60559 单一格式匹配。 double 类型与 IEC 60559 双格式匹配。 long double 类型匹配 IEC 60559 扩展格式,否则 非 IEC 60559 扩展格式,否则为 IEC 60559 double 格式。

此外,它说正常的算术必须遵循 IEC 60559 标准:

+*/ 运算符提供 IEC 60559 加、减、乘和除运算。

进一步要求sqrt 遵循IEC 60559:

<math.h> 中的 sqrt 函数提供 IEC 60559 平方根运算。

然后继续描述其他几个浮点函数的行为,您可能对这个问题不感兴趣。

最后,它到达math.h 标头,并指定各种数学函数(即sincosatan2exp 等)应如何处理特殊情况(即@987654341 @ 返回 ±0atanh(x) 返回 NaN 并引发 |x| > 1 的“无效”浮点异常等)。但它从未确定正常输入的精确计算,这意味着您不能依赖所有实现产生精确相同的计算。

所以不,它不需要这些函数在所有实现中表现相同,即使所有实现都定义了__STDC_IEC_559__


这都是从理论的角度来看的。在实践中,情况甚至更糟。 CPU 通常实现 IEC 60559 算术,但它可以有不同的舍入模式(因此结果会因计算机而异),并且编译器(取决于优化标志)可能会做出一些不符合严格标准的假设浮点运算。

因此,在实践中,它甚至比理论上更不严格,而且您很可能会看到两台计算机在某些时候产生略微不同的结果。


glibc 是一个真实的例子,它是 GNU C 库实现。 They have a table of known error limits for their math functions 跨不同的 CPU。如果所有 C 数学函数都是位精确的,那么这些表都将显示 0 个错误 ULP。但他们没有。表格显示他们的 C 数学函数确实存在不同程度的错误。我觉得这句话是最有趣的总结:

除了某些函数,如sqrtfmarint,其结果完全通过引用相应的 IEEE 754 浮点运算以及字符串和浮点之间的转换来指定,GNU C 库不旨在为数学库中的函数正确舍入结果[...]

glibc 中唯一位精确的东西是 C 标准的附件 F 要求位精确的东西。正如您在他们的表格中看到的那样,大多数情况并非如此。

【讨论】:

感谢您的详细解答。因此,为了保证相同的结果,需要编写自己的 pow 函数。 差不多。事实上,对于位精确性很重要的情况(例如在音频和视频编解码器中),实现自己的定点算法并不少见。 你的句子中的 正常输入 是什么意思“但它从来没有确定正常输入的确切计算”? @Zboson:我的意思是不应该产生任何错误或需要任何特殊处理的输入。例如,对于函数acos0.5 将被视为正常输入,因为该输入值的反余弦定义良好。但是输入1 需要特殊处理,因为它导致值为零,并且IEC 60559 有两个零:+0-0。附件 F 声明 acos(1) 应该产生 +0,而不是 -0。但附件 F 没有提及acos(0.5),因为那个“正常”输入值 不需要任何特殊处理。这样就清楚了吗? @Zboson:确实,您不能依赖 acos(0.5) 在不同的编译器中是完全相同的值。哎呀,你甚至不能依赖它与不同架构上的相同编译器相同。 glibc has a nice table listing the ULP error of their math functions,如您所见,这些数学函数并非在所有架构中都是位精确的。

以上是关于标准对 std::pow、std::log 等 cmath 函数有啥看法?的主要内容,如果未能解决你的问题,请参考以下文章

Qt每天一例11.QtMath类

static_assert 中的 std::pow 触发错误 C2057?

编程中系统允许对库函数重新定义吗?

C ++中非常快速的近似对数(自然对数)函数?

计算 p^q(求幂)的有效方法,其中 q 是整数

C语言文件操作