[解決済み] この浮動小数点演算の最適化は許されますか?
質問
私は
float
が大きな整数を正確に表現する能力を失っているところを調べてみました。そこで、こんな小さなスニペットを書いてみました。
int main() {
for (int i=0; ; i++) {
if ((float)i!=i) {
return i;
}
}
}
このコードはclangを除くすべてのコンパイラで動作するようです。Clangは単純な無限ループを生成します。 ゴッドボルト .
これは許可されているのでしょうか?もしそうなら、それはQoIの問題ですか?
どのように解決するのですか?
Angew が指摘したように
は、その
!=
演算子は両側で同じ型が必要です。
(float)i != i
は右辺を同様に float に昇格させるので、次のようになります。
(float)i != (float)i
.
g++も無限ループを生成しますが、その内部からの作業を最適化することはありません。 int->floatを変換しているのがわかると思います。
cvtsi2ss
で変換し
ucomiss xmm0,xmm0
を比較するために
(float)i
を自分自身と比較します。 (これが、@Angew の回答が説明するように、あなたの C++ ソースがあなたが考えていたような意味ではないという最初の手がかりでした)。
x != x
が真であるのは、それが "unordered"であるときだけだからです。
x
はNaNだったからです。 (
INFINITY
は IEEE の数学でそれ自身と等しく比較されますが、NaN はそうではありません。
NAN == NAN
は偽です。
NAN != NAN
は真)。
gcc7.4以前は、コードを正しく最適化して
jnp
をループブランチとして最適化します (
https://godbolt.org/z/fyOhW1
) : のオペランドがある限りループし続ける。
x != x
へのオペランドがNaNでない限り、ループを続ける。 (gcc8以降では
je
をチェックし、NaN でない入力に対しては常に真になるという事実に基づいて最適化することに失敗しました)。
ちなみに、つまり
clangの最適化も安全です。
: それはただCSEする必要があるだけです。
(float)i != (implicit conversion to float)i
が同じであることを証明し、さらに
i -> float
の取り得る範囲では決してNaNにならないことを証明する。
int
.
(このループが符号ありオーバーフローのUBにぶつかることを考えると、 文字通りどんなasmでも出すことが許されるのですが、その中には
ud2
不正な命令や、ループ本体が実際に何であったかに関係なく空の無限ループなど、文字通り好きなasmを出すことができます)。 しかし、符号付きオーバーフロー UB を無視しても、この最適化は 100% 合法です。
GCCはループ本体を取り除く最適化に失敗する
であっても
-fwrapv
を使って符号付き整数のオーバーフローをうまく定義しています。
(2の補数ラップアラウンドとして)。
https://godbolt.org/z/t9A8t_
を有効にしても
-fno-trapping-math
を有効にしても役に立ちません。 (GCCのデフォルトは
残念ながら
を有効にするために
-ftrapping-math
でも
GCCの実装は壊れている/バグがある
int->float 変換は FP の不正確な例外 (正確に表現できないほど大きな数) を引き起こす可能性があるので、例外がマスクされない可能性がある場合は、ループ本体を最適化しないことが合理的です。 (なぜなら
16777217
を float に変換すると、不正確な例外がマスクされない場合、観察可能な副作用を持つ可能性があるため)。
しかし
-O3 -fwrapv -fno-trapping-math
では、これを空の無限ループにコンパイルしないのは、100%ミス最適化です。 また
#pragma STDC FENV_ACCESS ON
がなければ、マスクされた FP 例外を記録するスティッキーフラグの状態は、コードの観察可能な副作用ではありません。 いいえ
int
->
float
の変換はNaNになる可能性があるので
x != x
は真になり得ない。
これらのコンパイラはすべて、IEEE 754 単精度 (binary32) を使用する C++ 実装に最適化されています。
float
と 32 ビット
int
.
は
バグフィックス
(int)(float)i != i
ループは、16 ビットの狭い C++ 実装では UB になっていました。
int
と広い
float
として正確に表現できない最初の整数に到達する前に、符号付き整数のオーバーフロー UB を起こしてしまうからです。
float
.
しかし、x86-64 System V ABI を持つ gcc や clang のような実装のためにコンパイルする場合、実装で定義された異なる選択肢の下で UB は何のマイナス効果もありません。
ところで、このループの結果を静的に計算することができるのは
FLT_RADIX
と
FLT_MANT_DIG
で定義されています。
<climits>
. 少なくとも理論的には、もし
float
がPosit / unumのような他の種類の実数表現ではなく、IEEE floatのモデルに実際に適合している場合、少なくとも理論的には可能です。
ISO C++ 標準規格がどの程度
float
の動作についてどの程度規定しているのか、また、固定幅の指数およびシグニフィカンドのフィールドに基づかないフォーマットが標準に準拠するのかどうかについては、よくわかりません。
コメントで
<ブロッククオート <ブロッククオート@geza 結果の数字が気になりますね~。
nada: 16777216です。
このループをprint / returnさせたと言うことでしょうか?
16777216
?
更新:そのコメントは削除されたので、私はそうではないと思います。 おそらく、OPは単に
float
の前に、32 ビットで正確に表現できない最初の整数の
float
.
https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values
つまり、このバグだらけのコードで何を検証しようとしたのか。
バグフィックス版では、もちろん
16777217
である最初の整数は
ではなく
が正確に表現できる最初の整数であり、その前の値ではありません。
(すべての高い浮動小数点値は正確な整数ですが、それらはシグニフィカンドの幅よりも高い指数値に対して、2の倍数、次に4、次に8などとなっています。 多くの高い整数値は表現できますが、(シグニフィカンドの)最後の場所の1単位は1より大きいので、連続した整数ではありません。 最大の有限の
float
は 2^128 のすぐ下で、これは大きすぎて
int64_t
.)
もし、どのコンパイラも元のループを終了してそれを表示したとしたら、それはコンパイラのバグでしょう。
関連
-
[解決済み】Visual Studio 2015で「非標準の構文; '&'を使用してメンバーへのポインターを作成します」エラー
-
[解決済み] 既に.objで定義されている-二重包含はない
-
[解決済み】エラー:不完全な型へのメンバーアクセス:前方宣言の
-
[解決済み】Eclipse IDEでC++エラー「nullptrはこのスコープで宣言されていません」が発生する件
-
[解決済み] 除算を強制的に浮動小数点にするにはどうしたらいいですか?除算は0に切り捨てられ続けますか?
-
[解決済み] JavaScriptで浮動小数点数の精度を扱うには?
-
[解決済み] なぜこのプログラムは3つのC++コンパイラで誤って拒否されるのですか?
-
[解決済み】浮動小数点値の比較はどのくらい危険か?
-
[解決済み】bashで浮動小数点演算を使用するには?
-
[解決済み】androidのリソース/値に浮動小数点値を追加する。
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++でユーザー入力を待つ【重複あり
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み】文字列関数で'char const*'のインスタンスを投げた後に呼び出されるterminate [閉店].
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み] 既に.objで定義されている-二重包含はない
-
[解決済み】ファイルから整数を読み込んで配列に格納する C++ 【クローズド
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】変数やフィールドがvoid宣言されている
-
[解決済み] C#でfloat変数が16777216でインクリメントを停止するのはなぜですか?