Norma IEEE 754-2008 dla arytmetyki zmiennoprzecinkowej oraz norma ISO / IEC 10967 dotycząca arytmetyki niezależnej od języka (LIA), część 1, odpowiadają, dlaczego tak jest.
IEEE 754 § 6.3. Bit znaku
Gdy dane wejściowe lub wynik to NaN, ten standard nie interpretuje znaku NaN. Należy jednak zauważyć, że operacje na łańcuchach bitowych - copy, negate, abs, copySign - określają bit znaku wyniku NaN, czasami oparty na bicie znaku operandu NaN. Na predykat logiczny totalOrder ma również wpływ bit znaku operandu NaN. Dla wszystkich innych operacji ten standard nie określa bitu znaku wyniku NaN, nawet jeśli istnieje tylko jeden wejściowy NaN lub gdy NaN jest wytwarzany z nieprawidłowej operacji.
Gdy ani dane wejściowe, ani wynik nie są NaN, znak iloczynu lub ilorazu jest wyłącznym LUB znaków argumentów; znak sumy lub różnicy x - y traktowanej jako suma x + (−y) różni się co najwyżej od jednego ze znaków addendów; a znak wyniku konwersji, operacji kwantyzacji, operacji roundTo-Integral i roundToIntegralExact (patrz 5.3.1) jest znakiem pierwszego lub jedynego operandu. Reguły te mają zastosowanie nawet wtedy, gdy argumenty lub wyniki są zerowe lub nieskończone.
Gdy suma dwóch operandów z przeciwnymi znakami (lub różnica dwóch argumentów z podobnymi znakami) jest równa dokładnie zero, znak tej sumy (lub różnicy) powinien wynosić +0 we wszystkich atrybutach kierunku zaokrąglania z wyjątkiem roundTowardNegative; pod tym atrybutem znak dokładnej sumy zerowej (lub różnicy) powinien wynosić -0. Jednak x + x = x - (−x) zachowuje ten sam znak co x, nawet gdy x jest równe zero.
Przypadek dodawania
W domyślnym trybie zaokrąglania (Round-to-Nearest, Ties-to-Even) , widzimy, że x+0.0
daje to x
, Z WYJĄTKIEM kiedy x
jest -0.0
: W takim przypadku mamy sumę dwóch operandów z przeciwnymi znakami, których suma wynosi zero, i § 6.3 akapit 3 zasady, które tworzy ten dodatek +0.0
.
Ponieważ +0.0
nie jest bitowo identyczny z oryginałem -0.0
i -0.0
jest to uzasadniona wartość, która może wystąpić jako dane wejściowe, kompilator jest zobowiązany do umieszczenia kodu, który przekształci potencjalne zera ujemne na +0.0
.
Podsumowanie: w domyślnym trybie zaokrąglania w x+0.0
, jeślix
- nie jest
-0.0
, to x
samo w sobie jest dopuszczalną wartością wyjściową.
- jest
-0.0
, to wartość wyjściowa musi być +0.0
, która nie jest bitowa identyczna z -0.0
.
Przypadek mnożenia
W domyślnym trybie zaokrąglania taki problem nie występuje w przypadku x*1.0
. Jeśli x
:
- jest (pod) normalną liczbą,
x*1.0 == x
zawsze.
- jest
+/- infinity
, to wynik jest +/- infinity
tego samego znaku.
jest NaN
, to zgodnie z
IEEE 754 § 6.2.3 Propagacja NaN
Operacja, która propaguje operand NaN do swojego wyniku i ma pojedynczy NaN jako dane wejściowe, powinna generować NaN z ładunkiem wejściowym NaN, jeśli można to przedstawić w formacie docelowym.
co oznacza, że wykładnik i mantysa (choć nie znakiem) od NaN*1.0
są zalecane do niezmienione od wejścia NaN
. Znak jest nieokreślony zgodnie z §6.3p1 powyżej, ale implementacja może określić, że jest identyczna ze źródłem NaN
.
- jest
+/- 0.0
, to wynikiem jest a 0
ze swoim bitem znaku XOR z bitem znaku 1.0
, zgodnie z §6.3p2. Ponieważ bit znaku wynosi 1.0
to 0
, wartość wyjściowa pozostaje niezmieniona w stosunku do wejścia. Zatem x*1.0 == x
nawet wtedy , gdy x
jest (ujemne) zero.
Przypadek odejmowania
W domyślnym trybie zaokrąglania odejmowanie x-0.0
jest również brakiem możliwości, ponieważ jest równoważne z x + (-0.0)
. Jeśli x
tak
- jest
NaN
, to §6.3p1 i §6.2.3 mają zastosowanie w podobny sposób jak do dodawania i mnożenia.
- jest
+/- infinity
, to wynik jest +/- infinity
tego samego znaku.
- jest (pod) normalną liczbą,
x-0.0 == x
zawsze.
- jest
-0.0
zatem przez §6.3p2 " [...] znak sumy lub różnicy x - y uważanej za sumę x + (−y) różni się od co najwyżej jednego ze znaków addendów; ”. To zmusza nas do przypisania -0.0
w rezultacie (-0.0) + (-0.0)
, bo -0.0
różni się oznaczeniem od żadnego z załączników, a +0.0
różni się znakiem od dwóch z załączników, z naruszeniem tej klauzuli.
- jest
+0.0
, to obniża się w przypadku dodawania (+0.0) + (-0.0)
badanego powyżej w przypadku dodania , który z §6.3p3 jest wykluczone, aby dać +0.0
.
Ponieważ we wszystkich przypadkach wartość wejściowa jest legalna jako wynik, dopuszczalne jest rozważenie x-0.0
braku operacji i x == x-0.0
tautologii.
Optymalizacje zmieniające wartość
Standard IEEE 754-2008 ma następujący interesujący cytat:
IEEE 754 § 10.4 Znaczenie dosłowne i optymalizacje zmieniające wartość
[…]
Między innymi następujące transformacje zmieniające wartość zachowują dosłowne znaczenie kodu źródłowego:
- Zastosowanie właściwości tożsamości 0 + x, gdy x nie jest zerem i nie jest sygnalizującym NaN, a wynik ma taki sam wykładnik jak x.
- Zastosowanie właściwości tożsamości 1 × x, gdy x nie jest sygnalizującym NaN, a wynik ma ten sam wykładnik co x.
- Zmiana ładunku lub bitu znaku cichego NaN.
- […]
Ponieważ wszystkie NaN i wszystkie nieskończoności mają ten sam wykładnik, a poprawnie zaokrąglony wynik x+0.0
i x*1.0
dla skończonych x
ma dokładnie taką samą wielkość jak x
ich wykładnik jest taki sam.
SNaNs
Sygnalizacja NaN to wartości pułapki zmiennoprzecinkowe; Są to specjalne wartości NaN, których użycie jako operandu zmiennoprzecinkowego powoduje wyjątek nieprawidłowej operacji (SIGFPE). Gdyby pętla wyzwalająca wyjątek została zoptymalizowana, oprogramowanie nie zachowywałoby się już tak samo.
Jednak, jak wskazuje user2357112 w komentarzach , standard C11 wyraźnie pozostawia niezdefiniowane zachowanie sygnalizacji NaN ( sNaN
), więc kompilator może założyć, że nie występują, a zatem wyjątki, które podnoszą, również nie występują. Standard C ++ 11 pomija opis zachowania przy sygnalizowaniu NaN, a zatem również pozostawia go niezdefiniowanym.
Tryby zaokrąglania
W alternatywnych trybach zaokrąglania dopuszczalne optymalizacje mogą ulec zmianie. Na przykład w trybie Round-to-Negative-Infinity optymalizacja x+0.0 -> x
staje się dozwolona, ale x-0.0 -> x
jest zabroniona.
Aby uniemożliwić GCC przyjmowanie domyślnych trybów i zachowań zaokrąglania, flagę eksperymentalną -frounding-math
można przekazać do GCC.
Wniosek
Clang i GCC , nawet w -O3
, pozostają zgodne z IEEE-754. Oznacza to, że musi przestrzegać powyższych zasad standardu IEEE-754. x+0.0
to nie nieco identyczne aby x
dla wszystkich x
w ramach tych zasad, ale x*1.0
mogą być wybierane tak : Mianowicie, kiedy
- Przestrzegaj zalecenia, aby przekazywać niezmieniony ładunek,
x
gdy jest to NaN.
- Pozostaw niezmieniony bit znaku wyniku NaN przez
* 1.0
.
- Rozkazu do XOR bit znaku podczas iloraz / produktu, gdy
x
jest nie NaN.
Aby włączyć optymalizację niebezpieczną dla IEEE-754 (x+0.0) -> x
, flaga -ffast-math
musi zostać przekazana do Clang lub GCC.