関数のインラインバージョンは、非インラインバージョンとは異なる値を返します


85

同じ関数の2つのバージョンが、一方がインラインでもう一方がインラインでないことだけが異なる場合、どうすれば異なる値を返すことができますか?これが私が今日書いたいくつかのコードですが、それがどのように機能するのかわかりません。

#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上のGCC7.1で問題なく動作します。
Diodacus

31
==浮動小数点値では常に少し予測できないのではありませんか?
500 -内部サーバーエラー


2
-Ofastそのような最適化を可能にするオプションを設定しましたか?
cmdLP

4
コンパイラはcbrt(27.0)の値を0x0000000000000840返しますが、標準ライブラリはを返します0x0100000000000840。ダブルスは、コンマの後の16番目の数字が異なります。私のシステム:archlinux4.20 x64 gcc8.2.1glibc2.28これでチェックしました。gccまたはglibcが正しいかどうか疑問に思います。
KamilCuk

回答:


73

説明

一部のコンパイラ(特にGCC)は、コンパイル時に式を評価するときに、より高い精度を使用します。式が定数入力とリテラルのみに依存している場合、式がconstexpr変数に割り当てられていなくても、コンパイル時に評価される可能性があります。これが発生するかどうかは、以下によって異なります。

  • 式の複雑さ
  • コンパイラがコンパイル時の評価を実行しようとするときにカットオフとして使用するしきい値
  • 特別な場合に使用されるその他のヒューリスティック(clangがループを削除する場合など)

最初のケースのように式が明示的に指定されている場合、式の複雑さは低くなり、コンパイラーはコンパイル時に式を評価する可能性があります。

同様に、関数がインラインとしてマークされている場合、インライン関数は評価が発生する可能性のあるしきい値を上げるため、コンパイラはコンパイル時にその関数を評価する可能性が高くなります。

-Ofastの例のように、最適化レベルを高くすると、このしきい値も増加します。この例では、コンパイル時の評価の精度が高いため、gccですべての式がtrueと評価されます。

この動作は、コンパイラエクスプローラで確認できます。-O1を使用してコンパイルすると、インラインとマークされた関数のみがコンパイル時に評価されますが、-O3では、両方の関数がコンパイル時に評価されます。

注意:コンパイラー・エクスプローラーの例では、printf代わりにiostreamを使用します。これは、メイン関数の複雑さが軽減され、効果がより見やすくなるためです。

それが実証inline実行時の評価には影響を与えません。

標準入力から値を取得することで、コンパイル時にどの式も評価されないようにすることができます。これを行うと、https//ideone.com/QZbv6Xに示すように、3つの式すべてがfalseを返します

#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

観察されたように、==演算子を使用して浮動小数点値を比較すると、さまざまなコンパイラーでさまざまな最適化レベルでさまざまな出力が得られました。

浮動小数点値を比較する良い方法の1つは、記事で概説されている相対許容誤差テストです。浮動小数点許容誤差の再検討

最初に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あるため、必須ではありません。しかし、私は、引数maxが互いに独立した値または式である可能性がある一般的なケースを検討しました。
PW

operator==(double, double)正確にそれを行うべきではありません。スケーリングされたイプシロンよりも差が小さいかどうかを確認してください。その場合、SOに関する浮動小数点関連の質問の約90%は存在しません。
ピーター-モニカを復活させる

ユーザーがEpsilon特定の要件に応じて値を指定できるとよいと思います。
PW
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.