函数的内联版本与非内联版本返回的值不同


85

同一函数的两个版本如何不同,它们的不同之处仅在于一个是内联函数而另一个不是内联函数?这是我今天编写的一些代码,我不确定它如何工作。

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    std::cout << (floor(cbrt(27.0)) == cbrt(27.0)) << std::endl;
    std::cout << (is_cube(27.0)) << std::endl;
    std::cout << (is_cube_inline(27.0)) << std::endl;
}

我希望所有输出都等于1,但实际上输出了此值(g ++ 8.3.1,没有标志):

1
0
1

代替

1
1
1

编辑:clang ++ 7.0.0输出此:

0
0
0

和g ++ -Ofast this:

1
1
1

3
您能提供什么编译器,使用的编译器选项以及什么机器?在Windows的GCC 7.1上可以正常工作。
Diodacus

31
==浮点值并非总是不可预测吗?
500-内部服务器错误,


2
您是否设置了-Ofast允许进行此类优化的选项?
cmdLP

4
编译器回报cbrt(27.0)的价值0x0000000000000840,而标准库的回报0x0100000000000840。双打在逗号后的第16个数字不同。我的系统:archlinux4.20 64 gcc8.2.1 glibc2.28经过与。想知道gcc还是glibc是正确的。
KamilCuk

Answers:


73

说明

一些编译器(尤其是GCC)在编译时评估表达式时使用更高的精度。如果表达式仅依赖于常量输入和文字,则即使未将表达式分配给constexpr变量,也可以在编译时对其求值。是否发生这种情况取决于:

  • 表达的复杂性
  • 尝试执行编译时间评估时,编译器用作临界值的阈值
  • 在特殊情况下(例如当clang elides循环时)使用的其他启发式方法

如果像第一种情况那样显式提供表达式,则它的复杂度较低,编译器可能会在编译时对其进行求值。

同样,如果将函数标记为内联,则编译器更有可能在编译时对其进行评估,因为内联函数会提高评估发生的阈值。

更高的优化级别也会增加此阈值,如-Ofast示例中那样,由于高精度的编译时评估,所有表达式在gcc上均评估为true。

我们可以在编译器资源管理器上观察到此行为。使用-O1进行编译时,仅在编译时评估标记为inline的函数,而在-O3时在编译时评估两个函数。

注意:在编译器探索器示例中,我printf改用iostream,因为它降低了主要功能的复杂性,从而使效果更明显。

证明这inline不会影响运行时评估

我们可以通过从标准输入中获取值来确保在编译时不对任何表达式求值,并且当我们这样做时,所有3个表达式都返回false,如下所示:https : //ideone.com/QZbv6X

#include <cmath>
#include <iostream>

bool is_cube(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}
 
bool inline is_cube_inline(double r)
{
    return floor(cbrt(r)) == cbrt(r);
}

int main()
{
    double value;
    std::cin >> value;
    std::cout << (floor(cbrt(value)) == cbrt(value)) << std::endl; // false
    std::cout << (is_cube(value)) << std::endl; // false
    std::cout << (is_cube_inline(value)) << std::endl; // false
}

本示例相反,在本示例中,我们使用相同的编译器设置,但在编译时提供了该值,从而获得了更高精度的编译时评估。


22

正如所观察到的,使用==运算符比较浮点值已导致使用不同的编译器和不同的优化级别得到不同的输出。

比较浮点值的一种好方法是文章中概述的相对公差测试:重新讨论了浮点公差

我们首先计算Epsilon相对公差)值,在这种情况下为:

double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();

然后以这种方式在内联函数和非内联函数中使用它:

return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);

现在的功能是:

bool is_cube(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();    
    return (std::fabs(std::floor(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

bool inline is_cube_inline(double r)
{
    double Epsilon = std::max(std::cbrt(r), std::floor(std::cbrt(r))) * std::numeric_limits<double>::epsilon();
    return (std::fabs(std::round(std::cbrt(r)) - std::cbrt(r)) < Epsilon);
}

现在,[1 1 1]使用不同的编译器和不同的优化级别,输出将如预期的那样()。

现场演示


max()通话的目的是什么?根据定义,floor(x)小于或等于x,所以max(x, floor(x))将始终等于x
肯·托马斯

@KenThomases:在这种特殊情况下,其中一个参数max只是另一个参数floor的,则不需要。但是我考虑了一个一般情况,其中to的参数max可以是彼此独立的值或表达式。
PW

难道operator==(double, double)不应该那样做,检查差异是否小于缩放的epsilon?那时,约有90%的SO浮点相关问题将不复存在。
彼得-恢复莫妮卡

我认为最好是用户Epsilon根据自己的特定要求指定值。
PW
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.