Jak zauważył @Angew , !=
operator potrzebuje tego samego typu po obu stronach.
(float)i != i
skutkuje również promocją RHS do float, więc mamy (float)i != (float)i
.
g ++ również generuje nieskończoną pętlę, ale nie optymalizuje pracy od wewnątrz. Możesz zobaczyć, że konwertuje int-> float zi cvtsi2ss
robi, ucomiss xmm0,xmm0
aby porównać (float)i
ze sobą. (To była twoja pierwsza wskazówka, że twoje źródło C ++ nie oznacza tego, co myślisz, że podobało się odpowiedź @ Angew.)
x != x
jest prawdziwy tylko wtedy, gdy jest „nieuporządkowany”, ponieważ x
był NaN. ( INFINITY
porównuje równe sobie w matematyce IEEE, ale NaN nie. NAN == NAN
jest fałszem, NAN != NAN
jest prawdą).
gcc7.4 i starsze poprawnie optymalizują twój kod jnp
jako gałąź pętli ( https://godbolt.org/z/fyOhW1 ): zachowaj pętlę tak długo, jak długo operandy x != x
nie były NaN. (gcc8 i późniejsze również sprawdzają je
, czy nie doszło do wyrwania się z pętli, nie optymalizując na podstawie faktu, że zawsze będzie to prawda dla każdego wejścia innego niż NaN). x86 FP porównuje ustawione PF na nieuporządkowanym.
A tak przy okazji, oznacza to, że optymalizacja clang jest również bezpieczna : po prostu musi CSE (float)i != (implicit conversion to float)i
być taka sama i udowodnić, że i -> float
nigdy nie jest NaN dla możliwego zakresu int
.
(Chociaż biorąc pod uwagę, że ta pętla uderzy w UB przepełnienia ze znakiem, może wyemitować dosłownie każdy asm, którego chce, w tym ud2
niedozwoloną instrukcję lub pustą nieskończoną pętlę, niezależnie od tego, czym faktycznie była treść pętli.) Ale ignorowanie UB przepełnienia ze znakiem , ta optymalizacja jest nadal w 100% legalna.
GCC nie udaje się zoptymalizować treści pętli, nawet jeśli -fwrapv
przepełnienie liczb całkowitych ze znakiem jest dobrze zdefiniowane (jako zawijanie dopełniacza 2). https://godbolt.org/z/t9A8t_
Nawet włączenie -fno-trapping-math
nie pomaga. (Domyślnym ustawieniem GCC jest niestety włączenie,
-ftrapping-math
mimo że jego implementacja w GCC jest zepsuta / błędna). Konwersja int-> float może spowodować niedokładny wyjątek FP (dla liczb zbyt dużych, aby były dokładnie reprezentowane), więc w przypadku wyjątków, które mogą zostać zdemaskowane, rozsądne jest nie zoptymalizować korpus pętli. (Ponieważ konwersja 16777217
do typu float może mieć obserwowalny efekt uboczny, jeśli niedokładny wyjątek zostanie zdemaskowany).
Ale w przypadku -O3 -fwrapv -fno-trapping-math
100% pominiętej optymalizacji nie kompilowanie tego do pustej nieskończonej pętli. Bez #pragma STDC FENV_ACCESS ON
stanu lepkich flag, które rejestrują zamaskowane wyjątki FP, nie można zaobserwować efektu ubocznego kodu. Nie int
-> float
konwersja może skutkować NaN, więc x != x
nie może być prawdą.
Wszystkie te kompilatory optymalizują pod kątem implementacji C ++, które używają pojedynczej precyzji IEEE 754 (binary32) float
i 32-bitowej int
.
Bugfixed(int)(float)i != i
pętla musiałaby UB na implementacje C ++ z wąskiej 16-bitowej int
i / lub szerszy float
, ponieważ chcesz trafić podpisał liczbą całkowitą przepełnienie przed UB osiągnięciu pierwszego liczbę całkowitą, która nie była dokładnie przedstawianego jako float
.
Ale UB w ramach innego zestawu wyborów zdefiniowanych w ramach implementacji nie ma żadnych negatywnych konsekwencji podczas kompilacji dla implementacji takiej jak gcc lub clang z x86-64 System V ABI.
Przy okazji, możesz statycznie obliczyć wynik tej pętli z FLT_RADIX
i FLT_MANT_DIG
, zdefiniowane w <climits>
. A przynajmniej możesz w teorii, jeśli float
faktycznie pasuje do modelu IEEE float, a nie innego rodzaju reprezentacji liczb rzeczywistych, takich jak Posit / unum.
Nie jestem pewien, jak bardzo standard ISO C ++ wpływa na float
zachowanie i czy format, który nie był oparty na wykładniku o stałej szerokości i polach istotności, byłby zgodny ze standardami.
W komentarzach:
@geza Chciałbym usłyszeć wynikową liczbę!
@nada: to 16777216
Czy twierdzisz, że masz tę pętlę do wydrukowania / zwrotu 16777216
?
Aktualizacja: ponieważ ten komentarz został usunięty, myślę, że nie. Prawdopodobnie OP po prostu cytuje float
przed pierwszą liczbą całkowitą, której nie można dokładnie przedstawić jako 32-bitowej float
. https://en.wikipedia.org/wiki/Single-precision_floating-point_format#Precision_limits_on_integer_values, czyli to, co chcieli zweryfikować za pomocą tego błędnego kodu.
Wersja z naprawionymi błędami oczywiście wypisuje 16777217
pierwszą liczbę całkowitą, której nie można dokładnie przedstawić, zamiast wartości poprzedzającej.
(Wszystkie wyższe wartości zmiennoprzecinkowe są dokładnymi liczbami całkowitymi, ale są to wielokrotności 2, następnie 4, następnie 8 itd. Dla wartości wykładników wyższych niż szerokość istotności. Można przedstawić wiele wyższych wartości całkowitych, ale 1 jednostka na ostatnim miejscu (znaczenia) jest większe niż 1, więc nie są one ciągłymi liczbami całkowitymi. Największa liczba skończona float
ma nieco mniej niż 2 ^ 128, co jest zbyt duże, aby było parzyste int64_t
).
Gdyby jakiś kompilator zakończył oryginalną pętlę i wydrukował to, byłby to błąd kompilatora.