Як зазначав @Angew , !=
оператору потрібні однакові типи з обох сторін.
(float)i != i
призводить до просування RHS до плаваючого, так що ми маємо (float)i != (float)i
.
g ++ також генерує нескінченний цикл, але це не оптимізує роботу всередині нього. Ви можете бачити, як він перетворює int-> float з cvtsi2ss
і робить ucomiss xmm0,xmm0
для порівняння (float)i
з собою. (Це був ваш перший підказка про те, що ваше джерело C ++ не означає те, що ви думали, як це, як пояснює відповідь @ Angew.)
x != x
вірно лише тоді, коли це "невпорядковано", оскільки це x
був NaN. ( INFINITY
порівнює рівне собі в математиці IEEE, але NaN - ні. NAN == NAN
неправда, NAN != NAN
істина).
gcc7.4 і старіші правильно оптимізують ваш код jnp
як гілку циклу ( https://godbolt.org/z/fyOhW1 ): продовжуйте цикл, доки операнди не x != x
були NaN. (gcc8 і пізніше також перевіряє, je
чи не виходить з циклу, не вдаючись оптимізувати, виходячи з того, що це завжди буде вірно для будь-якого входу, що не є NaN). x86 FP порівнює встановлений PF на невпорядкований.
До речі, це означає , що оптимізація clang також є безпечною : вона просто має CSE (float)i != (implicit conversion to float)i
однаковою і довести, що i -> float
ніколи не є NaN для можливого діапазону int
.
(Хоча з огляду на те, що цей цикл потрапить на UB із переповненим знаком, дозволено видавати буквально будь-який ASM, який він хоче, включаючи ud2
незаконну інструкцію або порожній нескінченний цикл, незалежно від того, яким тілом цикла був насправді.) Але ігноруючи 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
не може бути правдою.
Усі ці компілятори оптимізовані для реалізацій C ++, які використовують IEEE 754 одноточну (binary32) float
та 32-бітну int
.
Виправлена(int)(float)i != i
цикл матиме UB на реалізаціях C ++ з вузькими 16-бітами int
і / або ширше float
, тому що ви потрапили зареєстрований Целочисленное переповнення UB до досягнення першого цілого числа , яке не було точно уявімо як float
.
Але UB за іншого набору варіантів, визначених реалізацією, не має негативних наслідків при компіляції для реалізації, як gcc або clang, із системою x86-64 System V ABI.
До речі, ви можете статично обчислити результат цього циклу з FLT_RADIX
і FLT_MANT_DIG
, визначеного в <climits>
. Або, принаймні, ви можете теоретично, якщо float
насправді відповідає моделі плаваючого модуля IEEE, а не якомусь іншому виду подання реального числа, такому як Posit / unum.
Я не впевнений, наскільки стандарт ISO C ++ нагадує про float
поведінку та чи формат, який не базувався на показнику експоненти та значеннях фіксованої ширини, відповідав би стандартам.
У коментарях:
@geza Мені було б цікаво почути отриманий номер!
@nada: це 16777216
Ви стверджуєте, що отримали цей цикл для друку / повернення 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
.
Якщо якийсь компілятор вийшов із вихідного циклу та надрукував це, це була б помилка компілятора.