Wiem, że ==
sprawdzanie równości zmiennych zmiennoprzecinkowych nie jest dobrym sposobem. Ale chcę to wiedzieć za pomocą następujących stwierdzeń:
float x = ...
float y = x;
assert(y == x)
Skoro y
jest on kopiowany x
, czy twierdzenie to będzie prawdziwe?
c++
floating-point
Wei Li
źródło
źródło
-m32
), albo poinstruując GCC, aby użyła FPU x87 (-mfpmath=387
).Odpowiedzi:
Oprócz
assert(NaN==NaN);
przypadku wskazanego przez kmdreko, możesz mieć sytuacje z matematyką x87, kiedy 80-bitowe zmiennoprzecinkowe są tymczasowo przechowywane w pamięci, a później porównywane z wartościami, które są nadal przechowywane w rejestrze.Możliwy minimalny przykład, który kończy się niepowodzeniem z gcc9.2 po kompilacji z
-O2 -m32
:Demo Godbolt: https://godbolt.org/z/X-Xt4R
volatile
Prawdopodobnie mogą być pominięte, jeśli uda się stworzyć wystarczające ciśnienie do rejestru sąy
przechowywane i przeładowywane z pamięci (ale mylić wystarczająco kompilatora, aby nie pominąć porównania wszystko razem).Zobacz dokumentację GCC FAQ:
źródło
float
standardowej precyzji z dodatkową precyzją.-ffloat-store
wydaje się, że jest to sposób, aby temu zapobiec.Nie jest to prawdą, jeśli tak
x
jestNaN
, ponieważ porównaniaNaN
są zawsze fałszywe (tak, nawetNaN == NaN
). Dla wszystkich innych przypadków (wartości normalne, wartości nienormalne, nieskończoności, zera) to twierdzenie będzie prawdziwe.Porada dotycząca unikania liczb zmiennoprzecinkowych
==
dotyczy obliczeń, ponieważ liczby zmiennoprzecinkowe nie są w stanie wyrazić wielu wyników dokładnie wtedy, gdy są używane w wyrażeniach arytmetycznych. Przypisanie nie jest obliczeniem i nie ma powodu, aby przypisanie przyniosło inną wartość niż oryginał.Ocena o zwiększonej precyzji nie powinna stanowić problemu, jeśli przestrzegany jest standard. Z
<cfloat>
odziedziczony po C [5.2.4.2.2.8] ( moje podkreślenie ):Jednak, jak wskazano w komentarzach, niektóre przypadki z niektórymi kompilatorami, opcjami kompilacji i celami mogą sprawić, że paradoksalnie będzie to fałszywe.
źródło
x
zostanie obliczony w rejestrze w pierwszym wierszu, zachowując większą dokładność niż minimum dlafloat
.y = x
Może znajdować się w pamięci, zachowując jedyniefloat
precyzji. Następnie test równości zostałby wykonany z pamięcią względem rejestru, z różnymi dokładnościami, a zatem bez gwarancji.x+pow(b,2)==x+pow(a,3)
może się różnić od tego,auto one=x+pow(b,2); auto two=y+pow(a,3); one==two
że można porównać przy użyciu większej precyzji niż drugi (jeśli jeden / dwa są 64-bitowymi wartościami w ram, podczas gdy wartości intermediste są 80-bitowe na fpu). Czasami zadanie może coś zrobić.gcc -ffloat-store
dla ścisłej zgodności. Ale to pytanie dotyczyx=y; x==y;
tego, aby nie robić niczego, aby się między nimi różnić. Jeśliy
jest już zaokrąglony, aby pasował do liczby zmiennoprzecinkowej, konwersja na podwójne lub długie podwójne i odwrotne nie zmieni wartości. ...Tak, na
y
pewno przyjmie wartośćx
:Nie ma swobody w przypisywaniu innych wartości.
(Inni już wskazali, że porównanie równoważności
==
będzie jednak oceniaćfalse
dla wartości NaN).Zwykłym problemem w przypadku liczb zmiennoprzecinkowych
==
jest to, że łatwo nie mieć takiej wartości, jak myślisz. Tutaj wiemy, że dwie wartości, bez względu na to, jakie są, są takie same.źródło
[expr]
. Jeśli mam zignorować linki i skupić się na cytatach, to mam zamieszanie, że np. C.5.3 nie wydaje się dotyczyć użycia terminu „wartość” lub terminu „wynik” (chociaż tak jest użyj „wynik” raz w normalnym angielskim języku). Być może mógłbyś bardziej precyzyjnie opisać, gdzie według ciebie standard wprowadza rozróżnienie, i podać jeden wyraźny cytat z tego wydarzenia. Dzięki!Tak, we wszystkich przypadkach (pomijając problemy z NaN i x87), będzie to prawdą.
Jeśli to zrobisz
memcmp
, będziesz w stanie sprawdzić równość, jednocześnie porównując NaN i sNaN. Będzie to również wymagało od kompilatora pobrania adresu zmiennej, która zmusi wartość do 32-bitowejfloat
zamiast 80-bitowej. To wyeliminuje problemy z x87. Drugie stwierdzenie tutaj nie ma na celu wykazania, że==
nie porównuje NaN jako prawdy:Zauważ, że jeśli NaN mają inną reprezentację wewnętrzną (tj. Różną mantysę),
memcmp
to nie będzie porównywać prawdy.źródło
W zwykłych przypadkach byłoby to prawdą. (lub instrukcja assert nic nie zrobi)
Edytuj :
Przez „zwykłe przypadki” mam na myśli wykluczenie wyżej wymienionych scenariuszy (takich jak wartości NaN i jednostki zmiennoprzecinkowe 80x87) wskazanych przez innych użytkowników.
Biorąc pod uwagę przestarzałość 8087 układów w dzisiejszym kontekście, problem jest raczej odizolowany, a pytanie ma zastosowanie w obecnym stanie architektury zmiennoprzecinkowej, jest prawdziwe we wszystkich przypadkach z wyjątkiem NaN.
(odniesienie do 8087 - https://home.deec.uc.pt/~jlobo/tc/artofasm/ch14/ch143.htm )
Uznanie dla @chtz za odtworzenie dobrego przykładu i @kmdreko za wzmiankę o NaNs - wcześniej o nich nie wiedziałem!
źródło
x
aby być w rejestrze zmiennoprzecinkowym, gdyy
jest ładowany z pamięci. Pamięć może mieć mniejszą dokładność niż rejestr, co powoduje niepowodzenie porównania.float
wartość bez dodatkowej precyzji.int a=1; int b=a; assert( a==b );
wyrzucenie asercji, myślę, że sensowne jest udzielenie odpowiedzi na to pytanie tylko w odniesieniu do poprawnie działającego kompilatora (jednocześnie zauważając, że niektóre wersje niektórych kompilatorów mają / mają -been-wiadomo-to zrobić źle). W praktyce, jeśli z jakiegoś powodu kompilator nie usunie dodatkowej precyzji z wyniku przypisania do rejestru, powinien to zrobić, zanim użyje tej wartości.Tak, zawsze będzie zwracać wartość True , chyba że jest to NaN . Jeśli wartość zmiennej to NaN , zawsze zwraca False !
źródło