フロートを別の変数にコピーすると、それらは等しくなりますか?


167

を使用==して浮動小数点変数の等価性をチェックすることは良い方法ではありません。しかし、私は次のステートメントでそれを知りたいだけです:

float x = ...

float y = x;

assert(y == x)

yはからコピーされるのでx、アサーションはtrueになりますか?


78
実際のコードによるデモンストレーションによって実際に不平等を証明する人に50の報奨金を提供しましょう。80ビットと64ビットの動作を確認したい。さらに、1つの変数がレジスターにあり、他の変数がレジスターにないことを示す生成されたアセンブラーコードの説明のための別の50(または、不平等の理由が何であれ、低レベルで説明してほしい)。
トーマスウェラー

1
@ThomasWellerこれに関するGCCバグ:gcc.gnu.org/bugzilla/show_bug.cgi?id=323 ; ただし、x86-64システムで再現しようとしたところ、-ffast-mathを使用しても再現されませんでした。32ビットシステムでは古いGCCが必要だと思います。
pjc50

5
@ pjc50:実際には、バグ323を再現するには80ビットシステムが必要です。問題を引き起こしたのは80x87 FPUです。x86-64はSSE FPUを使用します。余分なビットは問題を引き起こします。それは、32ビットの浮動小数点に値をスピルするときに丸められるためです。
MSalters

4
MSaltersの理論が正しい場合(そして私はそうだと思います)、32ビット用にコンパイルする(-m32)か、GCCにx87 FPUを使用するように指示する()ことで再現できます-mfpmath=387
コーディグレイ

4
「48ビット」を「80ビット」に変更すると、「神話的」形容詞@Hotを削除できます。それはまさにあなたのコメントの直前に議論されていたものです。x87(x86アーキテクチャのFPU)は、「拡張精度」フォーマットの80ビットレジスタを使用します。
コーディグレイ

回答:


125

assert(NaN==NaN);kmdrekoによって指摘されたケースに加えて、x87-mathで80ビット浮動小数点が一時的にメモリに格納され、後でレジスター内にまだ格納されている値と比較される場合があります。

可能な最小の例-O2 -m32:でコンパイルするとgcc9.2で失敗します:

#include <cassert>

int main(int argc, char**){
    float x = 1.f/(argc+2);
    volatile float y = x;
    assert(x==y);
}

Godboltデモ:https ://godbolt.org/z/X-Xt4R

volatileあなたは十分なレジスタ圧力を作成するために管理している場合、おそらくしているために、省略することができますy保存され、メモリから再ロード(ただし、コンパイラは十分、すべて一緒に比較を省略しないように混乱します)。

GCC FAQリファレンスを参照してください:


2
float標準の精度と余分な精度を比較するときに、余分なビットが考慮されるのは奇妙に思われます。
Nat

13
@Natそれ奇妙です。これはバグです。
オービットでのライトネスレース

13
@ThomasWellerいいえ、それは妥当な賞です。私は、これは非準拠の動作であることを指摘して答えをしたいと思いますけれども
軌道上での明度レース

4
私はこの答えを拡張して、アセンブリコードで正確に何が起こるか、そしてこれが実際に標準に違反していることを指摘することができます-私は自分を言語弁護士とは呼ばないので、あいまいなものがないことを保証できませんその動作を明示的に許可する句。私は、OPが完全にバグのない完全に準拠したコンパイラ(事実上存在しないと思います)ではなく、実際のコンパイラの実際の複雑さにもっと関心を持っていたと思います。
1

4
-ffloat-storeこれはこれを防ぐ方法のように思われることに言及する価値があります。
OrangeDog

116

onの比較は常にfalse(yesでさえ)であるため、xisの場合はtrueになりません。他のすべてのケース(通常値、非正規値、無限大、ゼロ)の場合、このアサーションは真になります。NaNNaNNaN == NaN

浮動小数点を回避する==ためのアドバイスは、浮動小数点数が算術式で使用されると多くの結果を正確に表現できないため、計算に適用されます。割り当ては計算ではなく、割り当てが元の値と異なる値になる理由はありません。


標準に準拠している場合、拡張精度評価は問題にはなりません。<cfloat>C からの継承[5.2.4.2.2.8](強調鉱山):

割り当てとキャスト(余分な範囲と精度をすべて削除する)を除いて、通常の算術変換の対象となる浮動オペランドと値を持つ演算の値と浮動定数の値は、範囲と精度がタイプ。

ただし、コメントで指摘されているように、特定のコンパイラ、ビルドオプション、およびターゲットを使用した場合、これが逆説的に誤りになることがあります。


10
x最初の行のレジスタで計算され、aの最小値よりも高い精度を維持するとどうなりますかfloaty = x唯一の維持、メモリであってもよいfloat精度。次に、同等性のテストは、レジスタに対してメモリを使用して、異なる精度で行われるため、保証はありません。
David Schwartz

5
x+pow(b,2)==x+pow(a,3)auto one=x+pow(b,2); auto two=y+pow(a,3); one==two一方が他方よりも高い精度を使用して比較する可能性があるため、異なる可能性があります(1/2が64ビット値のRAMであり、中間値がfpuで80ビットの場合)。したがって、割り当ては時々何かを行うことができます。
Yakk-Adam Nevraumont

22
@evgもちろん!私の答えは単純に標準に従います。特に高速演算を有効にする場合に、コンパイラーを非コンフォームにするように指示すると、すべての賭けが無効になります。
kmdreko

11
@Voo私の答えの引用を参照してください。RHSの値は、LHSの変数に割り当てられます。結果として得られるLHSの値がRHSの値と異なるという正当な理由はありません。いくつかのコンパイラにこの点でバグがあることを感謝しています。しかし、何かがレジスターに格納されているかどうかは、それとは何の関係もないはずです。
オービット

6
@Voo:ISO C ++では、型の幅への丸めはすべての割り当てで発生するはずです。x87をターゲットとするほとんどのコンパイラーでは、コンパイラーがスピル/リロードを決定した場合にのみ発生します。gcc -ffloat-store厳密なコンプライアンスのためにそれを強制することができます。しかし、この質問はx=y; x==y; 、どちらの変数に対しても何もしないことに関するものです。 がfloatに収まるように丸められている場合y、doubleまたはlong doubleに変換してから戻すと、値は変更されません。...
Peter Cordes

34

はい、y確実に次の価値を引き受けますx

[expr.ass]/2:単純な代入(=)では、左のオペランドによって参照されるオブジェクトは、その値を右のオペランドの結果に置き換えることによって変更されます([defns.access])。

他の値を割り当てる余裕はありません。

(他の人たちは、等価比較==がそれでもfalseNaN値に対して評価されることをすでに指摘しています。)

浮動小数点に関する通常の問題==は、あなたが思っているほどの価値を持たないことは簡単なことです。ここでは、2つの値が同じであることを知っています。


7
@ThomasWeller結果として非準拠の実装における既知のバグです。それを言及するのは良いことです!
オービットでのライトネスレース

最初は、「価値」と「結果」の区別を弁護士で扱う言語は不可解だと思いましたが、この区別はC2.2、7.1.6の言語による違いがない限り必須ではありません。C3.3、7.1.6; 引用するドラフト規格のC4.2、7.1.6、またはC5.3、7.1.6。
エリックタワーズ

@EricTowers申し訳ありませんが、これらの参照を明確にできますか?私はあなたが指しているものを見つけていません
オービットのライトネスレース

@ LightnessRacesBY-SA3.0:CC2.2C3.3C4.2、およびC5.3
エリックタワーズ

@EricTowersええ、まだあなたをフォローしていません。最初のリンクは、Appendix Cインデックスへのリンクです(何も教えてくれません)。次の4つのリンクはすべてに移動し[expr]ます。リンクを無視して引用に焦点を合わせると、たとえばC.5.3が「値」という用語や「結果」という用語の使用に対応していないように見えるという混乱が残ります(ただし、通常の英語のコンテキストで「結果」を1回使用します)。おそらく、標準によって区別されると思われる場所をより明確に記述し、これが起こっていることを明確に引用することができます。ありがとう!
オービット

3

はい、すべての場合(NaNとx87の問題を無視)、これは当てはまります。

そうした場合memcmp、それらの上にNaNとsNaNsを比較することでありながら、あなたは平等のためにできるテストになります。これには、値をfloat80 ビットではなく32ビットに強制する変数のアドレスを取得するコンパイラも必要になります。これにより、x87の問題が解消されます。ここでの2番目のアサーション==は、NaNをtrueと比較しないことを示さないようにすることを目的としています。

#include <cmath>
#include <cassert>
#include <cstring>

int main(void)
{
    float x = std::nan("");
    float y = x;
    assert(!std::memcmp(&y, &x, sizeof(float)));
    assert(y == x);
    return 0;
}

NaNの内部表現が異なる(仮数が異なる)場合、memcmptrueは比較されないことに注意してください。


1

通常の場合は、trueと評価されます。(またはassertステートメントは何もしません)

編集

「通常のケース」とは、他のユーザーから指摘された前述のシナリオ(NaN値や80x87浮動小数点単位など)を除外することを意味します。

今日の状況で8087チップが陳腐化していることを考えると、問題はかなり孤立しており、使用される浮動小数点アーキテクチャの現在の状態に問題が当てはまるため、NaNを除くすべてのケースに当てはまります。

(8087に関するリファレンス-https ://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm

良い例を再現した@chtzとNaNについて言及した@kmdrekoへの称賛-以前はそれらについて知らなかった!


1
がメモリからロードxされている間yは、浮動小数点レジスタに入ることが完全に可能であると思いました。メモリはレジスタよりも精度が低く、比較が失敗する可能性があります。
David Schwartz

1
それは偽りの一例かもしれません、私はそんなに考えていませんでした。(OPは特別なケースを提供しなかったので、追加の制約はないと想定しています)
Anirban166

1
あなたの言っていることがよくわかりません。私が質問を理解しているように、OPはfloatをコピーするかどうかを尋ねており、等価性のテストが成功することが保証されています。あなたの答えは「はい」と言っているようです。私はなぜ答えがノーではないのかを尋ねています。
David Schwartz

6
編集により、この回答は正しくありません。C ++標準では、割り当てによって値を宛先の型に変換する必要があります。式の評価で過剰な精度を使用することはできますが、割り当てを通じて保持することはできません。値がレジスタとメモリのどちらに保持されているかは重要ではありません。C ++標準では、コードが記述されているため、float余分な精度のない値である必要があります。
Eric Postpischil

2
@AProgrammer(極端に)バギーなコンパイラが理論的にint a=1; int b=a; assert( a==b );アサーションをスローする可能性があることを考えると、正しく機能しているコンパイラに関してこの質問に答えるのは理にかなっていると思います(ただし、一部のコンパイラのバージョンによっては、 -これを誤解することがわかっている)。実際には、何らかの理由でコンパイラがレジスタに格納された割り当ての結果から余分な精度を削除しない場合、その値を使用する前に削除する必要があります。
トライプハウンド

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.