Як зазначав @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.
Якщо якийсь компілятор вийшов із вихідного циклу та надрукував це, це була б помилка компілятора.