Как отметил @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чтобы сделать целочисленное переполнение со знаком четко определенным (как двойное дополнение). https://godbolt.org/z/t9A8t_
Даже включение -fno-trapping-mathне помогает. (По умолчанию GCC, к сожалению, включен,
-ftrapping-mathхотя его реализация в GCC не работает / содержит ошибки .) Преобразование int-> float может вызвать неточное исключение FP (для чисел, слишком больших для точного представления), поэтому с исключениями, которые могут быть размаскированы, разумно не оптимизировать тело цикла. (Поскольку преобразование 16777217в число с плавающей точкой может иметь заметный побочный эффект, если неточное исключение разоблачено.)
Но с -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.)
Если какой-либо компилятор выйдет из исходного цикла и напечатает его, это будет ошибкой компилятора.