この浮動小数点最適化は許可されていますか?


90

私は、float大きな整数を正確に表す能力を失う場所を調べてみました。だから私はこの小さなスニペットを書きました:

int main() {
    for (int i=0; ; i++) {
        if ((float)i!=i) {
            return i;
        }
    }
}

このコードは、clangを除くすべてのコンパイラで機能するようです。Clangは単純な無限ループを生成します。ゴッドボルト

これは許可されますか?はいの場合、QoIの問題ですか?


@geza結果の数を聞きたいです!

5
gcc-Ofast代わりにでコンパイルすると、同じ無限ループ最適化を実行するため、最適化gccは安全ではないと見なされますが、それは可能です。
12345ieee

3
g ++も無限ループを生成しますが、内部からの作業を最適化しません。あなたはそれがそれ自身ucomiss xmm0,xmm0と比較するために行うのを見ることができます(float)i。これは、C ++ソースがあなたが思ったとおりに機能していないという最初の手がかりでした。このループを印刷/返送したと主張しています16777216か?それにはどのコンパイラ/バージョン/オプションがありましたか?それはコンパイラのバグだからです。gccが正しくにあなたのコードを最適化jnpループブランチ(としてgodbolt.org/z/XJYWeuはオペランドがする限り、ループ保つ:)!= はNaNませんでした。
Peter Cordes

4
具体的には、GCCが安全でない浮動小数点最適化を適用し、Clangと同じコードを生成できるようにすることで、-ffast-math暗黙的に有効になるオプションです-Ofast。MSVCはまったく同じように動作します。がなければ/fp:fast、無限ループになるコードの束を生成します。では/fp:fast、単一のjmp命令を発行します。安全でないFP最適化を明示的にオンにしないと、これらのコンパイラーは、NaN値に関するIEEE 754要件でハングアップすることを想定しています。実際には、Clangがそうではないことはかなり興味深いです。その静的アナライザーは優れています。@ 12345ieee
コーディグレイ

1
@geza:コードが意図したとおりに実行され、の数学的な値がの数学的な値と(float) i異なる場合をチェックするとi、結果(returnステートメントで返される値)は16,777,217ではなく、16,777,217になります。
Eric Postpischil

回答:


49

@Angewが指摘したように、!=オペレーターは両側に同じ型が必要です。 (float)i != iフロートへのRHSの促進に結果だけでなく、私たちは持っています(float)i != (float)i


g ++も無限ループを生成しますが、内部からの作業を最適化しません。あなたはそれがでINT-> floatを変換見ることができるcvtsi2ssとしucomiss xmm0,xmm0比較すること(float)i自体に。(これは、C ++ソースが、@ Angewの答えが説明するように、それが何をしたと思ったのかを意味しないという最初の手がかりでした。)

x != xxNaNであったため、「順序付けなし」の場合にのみ当てはまります。(INFINITYIEEE数学ではそれ自体と同じですが、NaNはそうでNAN == NANはありません。false 、NAN != NANtrueです)。

gcc7.4以前のバージョンでは、コードをjnpループブランチ(https://godbolt.org/z/fyOhW1)として正しく最適化しますx != x 。対象のオペランドがNaNでない限り、ル​​ープを続けます。(gcc8以降jeでは、ループの脱出もチェックされ、NaN以外の入力に対して常にtrueであるという事実に基づいて最適化に失敗します)。x86 FPは、順不同でセットPFを比較します。


そしてところで、その手段打ち鳴らすの最適化にも安全である:それはちょうどCSEにあり(float)i != (implicit conversion to float)i、同じであるとして、それは証明i -> floatの可能な範囲のためにはNaNになることはありませんint

(このループはsigned-overflow UBにヒットすることを前提としていますがud2、ループ本体が実際に何であるかに関係なく、不正な命令や空の無限ループなど、必要なasmを文字どおりに発行することができます。)ただし、signed-overflow UBは無視します。 、この最適化は依然として100%正当です。


GCCは、ループ本体を離れて最適化に失敗しさえして-fwrapv明確に定義された符号付き整数オーバーフローするために(2の補数回り込みように)。 https://godbolt.org/z/t9A8t_

有効に-fno-trapping-mathしても役に立ちません。(GCCのデフォルトは、GCCの実装が壊れているかバギーであるにもかかわらず、残念ながら有効になっ
-ftrapping-mathています。)int-> float変換は、FPの不正確な例外を引き起こす可能性があるため(数値が大きすぎて正確に表現できない場合)、例外がマスクされていない場合、ループ本体を最適化します。(浮動小数点への変換は、不正確な例外がマスクされていない場合、目に見える副作用をもたらす可能性があるためです。)16777217

しかし、-O3 -fwrapv -fno-trapping-mathでは、これを空の無限ループにコンパイルしないと、最適化が100%失敗します。なし#pragma STDC FENV_ACCESS ONでは、マスクされたFP例外を記録するスティッキーフラグの状態は、コードの観察可能な副作用ではありません。いいえint-> float変換の結果はNaNになるx != x可能性があるため、trueにすることはできません。


これらのコンパイラーはすべて、IEEE 754単精度(binary32)floatおよび32ビットを使用するC ++実装用に最適化されていますint

バグフィックス(int)(float)i != iループは狭い16ビットとC ++の実装上のUBを持つことになりint、および/またはより広いfloatあなたがヒットすると思いますので、符号付き整数オーバーフローUBとして正確に表現できなかった最初の整数に到達する前にfloat

ただし、x86-64 System V ABIを使用してgccやclangなどの実装用にコンパイルする場合、実装で定義された別の選択肢のセットの下にあるUBは悪影響を及ぼしません。


ところで、このループの結果は、で定義されているFLT_RADIXとから静的に計算できます。または、少なくとも理論的には、Posit / unumのような他の種類の実数表現ではなく、IEEE floatのモデルに実際に適合する場合は、可能です。FLT_MANT_DIG<climits>float

ISO C ++標準がどのようにfloat動作について規定されているか、および固定幅の指数と仮数フィールドに基づいていない形式が標準に準拠しているかどうかはわかりません。


コメントで:

@geza結果の番号を聞きたいです。

@nada:16777216

このループを印刷/返送したと主張しています16777216か?

更新:そのコメントは削除されたので、私はそうは思いません。おそらく、OPは単にfloat32ビットとして正確に表現できない最初の整数の前を引用していますfloathttps://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values つまり、このバグのあるコードで確認したかったことです。

もちろん16777217、バグ修正されたバージョンは、その前の値ではなく、正確に表現できない最初の整数であるprint になります。

(すべての高い浮動小数点値は正確な整数ですが、有効桁数よりも大きい指数値の場合、2、4、8の倍数などです。多くのより高い整数値を表すことができますが、最後は1単位です。 (仮数の)は1より大きいため、連続した整数ではありません。最大の有限値floatは2 ^ 128未満であり、には大きすぎint64_tます。)

コンパイラが元のループを終了して出力した場合は、コンパイラのバグです。


3
@SombreroChicken:いいえ、私は最初に電子工学を学び(私の父がうそをついていたいくつかの教科書から、彼は物理学の教授でした)、次にデジタルロジックを学び、その後CPU /ソフトウェアに入りました。:Pだから、私はいつも最初から物事を理解するのが好きだった、または私がより高いレベルから始めるなら、私は自分がいるレベルで物事がどのように/なぜ機能するかに影響する少なくとも下のレベルについて何かを学びたいについて考える。(例:asmの動作と最適化の方法は、CPU設計の制約/ cpu-architectureの影響を受けます。これは、物理学+数学に由来します。)
Peter Cordes

1
GCCはでも最適化できない場合がありますがfrapw、GCC 10 -ffinite-loopsはこのような状況向けに設計されたと確信しています。
MCCCS

64

組み込み演算子で!=は、オペランドが同じ型である必要があり、必要に応じて昇格と変換を使用してその演算子を実現することに注意してください。言い換えれば、あなたの状態は以下と同等です:

(float)i != (float)i

これは決して失敗してはならず、コードは最終的にオーバーフローしi、プログラムに未定義の動作を与えます。したがって、あらゆる動作が可能です。

確認したいものを正しく確認するには、結果を次のようにキャストする必要がありますint

if ((int)(float)i != i)

8
@DžurisUBです。ありません誰も明確な結果が。コンパイラーはUBでしか終了できないことを認識し、ループを完全に削除することを決定する場合があります。
モニカの訴訟に資金

4
@opaはどういう意味static_cast<int>(static_cast<float>(i))ですか?reinterpret_cast明白なUBです
Caleth

6
@NicHartley:(int)(float)i != iUBって言ってるの?それをどのように結論付けますか?はい、それは実装で定義されたプロパティに依存します(floatIEEE754 binary32である必要がないため)。ただし、特定の実装でfloatは、すべての正のint値を正確に表すことができない限り、明確に定義されているため、符号付き整数のオーバーフローUBを取得できます。(en.cppreference.com/w/cpp/types/climitsがこれを定義FLT_RADIXおよびFLT_MANT_DIG決定します)。一般的な印刷の実装で定義されたものなど、std::cout << sizeof(int)UBではない
Peter Cordes

2
@Caleth:reinterpret_cast<int>(float)正確にはUBではなく、単なる構文エラー/ 不正な形式です。その構文が(明確に定義されている)のint代わりにfloatの型パンニングを許可していればいいのですmemcpyreinterpret_cast<>、ポインタ型でのみ機能すると思います。
Peter Cordes

2
@ピーターNaNにとってx != xは本当です。コリルでのライブをご覧ください。Cでも。
Deduplicator
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.