Почему MSVS не оптимизирует прочь +0?

Этот вопрос демонстрирует очень интересное явление: денормализованные числа с плавающей точкой замедляют код более чем на порядок.

Поведение хорошо объясняется в принятом ответе. Тем не менее, есть один комментарий с 153 отзывами, на который я не могу найти удовлетворительный ответ:

Почему в этом случае компилятор просто не удаляет + / - 0?!? - Майкл Дорган

Примечание: у меня сложилось впечатление, что 0f является/должно быть точно представимым (более того - это двоичное представление должно быть всеми нулями), но я не могу найти такое утверждение в стандарте c11.Цитата, доказывающая это, или аргумент, опровергающий это утверждение, было бы очень желательно.Несмотря на это, Майкл вопрос является основным вопросом здесь.


§5.2.4.2.2

Реализация может дать ноль и значения, которые не являются числами с плавающей точкой (например, бесконечности и NaN), знак или могут оставить их без знака.

Ответ 1

Компилятор не может устранить добавление положительного нуля с плавающей запятой, потому что это не операция идентификации. По правилам IEEE 754 результат добавления +0. до -0. не -0; это +0.

Компилятор может исключить вычитание +0. или добавлением -0. потому что это операции с идентификаторами.

Например, когда я скомпилирую это:

double foo(double x) { return x + 0.; }

с Apple GNU C 4.2.1 с использованием -O3 на Intel Mac, итоговый код сборки содержит addsd LC0(%rip), %xmm0. Когда я скомпилирую это:

double foo(double x) { return x - 0.; }

нет инструкции добавления; сборка просто возвращает свой вход.

Итак, скорее всего, код в исходном вопросе содержал инструкцию add для этого оператора:

y[i] = y[i] + 0;

но не содержит инструкции для этого оператора:

y[i] = y[i] - 0;

Однако первый оператор включал арифметику с субнормальными значениями в y[i], поэтому было достаточно замедлить работу программы.

Ответ 2

Это не нулевая постоянная 0.0f которая денормализована, это значения, которые приближаются к нулю на каждой итерации цикла. По мере того как они становятся ближе и ближе к нулю, им нужно больше точности для представления, следовательно, денормализация. В первоначальном вопросе это значения y[i].

Принципиальное различие между медленной и быстрой версиями кода заключается в выражении y[i] = y[i] + 0.1f; , Как только эта строка выполняется, дополнительная точность в плавающей точке теряется, и денормализация, необходимая для представления этой точности, больше не нужна. После этого операции с плавающей точкой на y[i] остаются быстрыми, потому что они не денормализованы.

Почему лишняя точность теряется при добавлении 0.1f? Потому что числа с плавающей запятой имеют только столько значащих цифр. Скажем, у вас достаточно места для хранения трех значащих цифр, затем 0.00001 = 1e-5 и 0.00001 + 0.1 = 0.1, по крайней мере, для этого примера формата с плавающей запятой, поскольку в нем нет места для хранения 0.10001 бита в 0.10001.