Как отметил @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
.)
Если какой-либо компилятор выйдет из исходного цикла и напечатает его, это будет ошибкой компилятора.