Стандарт IEEE 754-2008 для арифметики с плавающей запятой и стандарт независимой от языка арифметики (LIA) ISO / IEC 10967, часть 1 дают ответы, почему это так.
IEEE 754 § 6.3 Знаковый бит
Когда вход или результат - NaN, этот стандарт не интерпретирует знак NaN. Однако обратите внимание, что операции с битовыми строками - copy, negate, abs, copySign - определяют бит знака результата NaN, иногда основанный на бите знака операнда NaN. На логический предикат totalOrder также влияет знаковый бит операнда NaN. Для всех других операций этот стандарт не определяет знаковый бит результата NaN, даже если имеется только одно входное NaN или когда NaN создается в результате недопустимой операции.
Когда ни входы, ни результат не являются NaN, знак произведения или частного является исключающим ИЛИ знаков операндов; знак суммы или разности x - y, рассматриваемой как сумма x + (−y), отличается не более чем от одного из знаков слагаемых; а знак результата преобразований, операции квантования, операций roundTo-Integral и roundToIntegralExact (см. 5.3.1) является знаком первого или единственного операнда. Эти правила применяются, даже если операнды или результаты равны нулю или бесконечны.
Когда сумма двух операндов с противоположными знаками (или разность двух операндов с одинаковыми знаками) равна нулю, знак этой суммы (или разности) должен быть +0 во всех атрибутах направления округления, кроме roundTowardNegative; под этим атрибутом знак точной нулевой суммы (или разности) должен быть -0. Однако x + x = x - (−x) сохраняет тот же знак, что и x, даже если x равен нулю.
Случай сложения
В режиме по умолчанию округление (Round-к-ближняя, Галстуки-к-четно) , мы видим , что x+0.0производит x, кроме случаев , когда xэто -0.0: В этом случае мы имеем сумму двух операндов с противоположными знаками, сумма которых равна нулю, а в пункте §6.3 3 правила, которые дает это дополнение+0.0 .
Так +0.0как не является побитовым идентичным оригиналу -0.0и -0.0это допустимое значение, которое может быть введено в качестве входных данных, компилятор обязан ввести код, который преобразует потенциальные отрицательные нули в +0.0.
Сводка: в режиме округления по умолчанию, в x+0.0, еслиx
- нет
-0.0 , то xсам по себе является приемлемым выходным значением.
- есть
-0.0 , то выходное значение должно быть +0.0 , что не побитово идентично -0.0.
Случай умножения
В режиме округления по умолчанию такой проблемы не возникает с x*1.0. Если x:
x*1.0 == xвсегда является (суб) нормальным числом .
- есть
+/- infinity, то результат +/- infinityтого же знака.
есть NaN, то согласно
IEEE 754 § 6.2.3 Распространение NaN
Операция, которая распространяет операнд NaN на свой результат и имеет единственный NaN в качестве входных данных, должна давать NaN с полезной нагрузкой входного NaN, если она представлена в формате назначения.
что означает , что показатель и мантисса (хотя и не знак) NaN*1.0являются рекомендуется , чтобы быть неизменными от входа NaN. Знак не указан в соответствии с §6.3p1 выше, но реализация может указать, что он идентичен источникуNaN .
- есть
+/- 0.0, то результатом будет 0знаковый бит, объединенный XOR со знаковым битом 1.0, в соответствии с §6.3p2. Поскольку знаковый бит 1.0равен 0, выходное значение не отличается от входного. Таким образом, x*1.0 == xдаже когда xявляется (отрицательным) нулем.
Случай вычитания
В режиме округления по умолчанию вычитание x-0.0также не выполняется, потому что оно эквивалентно x + (-0.0). Еслиx это
- является
NaN , то §6.3p1 и §6.2.3 применяются почти так же, как для сложения и умножения.
- является
+/- infinity, то результат +/- infinityтого же знака.
- является (суб) нормальным числом,
x-0.0 == xвсегда .
- есть
-0.0, то согласно §6.3p2 мы имеем « [...] знак суммы или разности x - y, рассматриваемой как сумма x + (−y), отличается не более чем от одного из знаков слагаемых; ». Это заставляет нас присваивать -0.0как результат (-0.0) + (-0.0), потому что не -0.0отличается по знаку ни от одного слагаемого, а +0.0по знаку отличается от двух слагаемых в нарушение этого пункта.
- это
+0.0, то это сводится к случаю сложения , (+0.0) + (-0.0)рассмотренной выше в случае добавления , который по §6.3p3 правят , чтобы дать +0.0.
Поскольку для всех случаев входное значение является допустимым в качестве выходного, допустимо рассматривать x-0.0 отсутствие операции и x == x-0.0тавтологию.
Оптимизация, меняющая ценность
В стандарте IEEE 754-2008 есть следующая интересная цитата:
IEEE 754 § 10.4 Оптимизация буквального значения и изменения значений
[...]
Следующие трансформации, изменяющие значение, среди прочего, сохраняют буквальное значение исходного кода:
- Применение свойства идентичности 0 + x, когда x не равно нулю и не является сигнальным NaN, и результат имеет тот же показатель степени, что и x.
- Применение свойства идентичности 1 × x, когда x не является сигнальным NaN и результат имеет тот же показатель степени, что и x.
- Изменение полезной нагрузки или бита знака тихого NaN.
- [...]
Поскольку все NaN и все бесконечности имеют один и тот же показатель степени, и правильно округленный результат x+0.0и x*1.0для конечного xимеет точно такую же величину, что иx , их показатель степени такой же.
sNaNs
Сигнальные NaN - это значения прерывания с плавающей запятой; Это особые значения NaN, использование которых в качестве операнда с плавающей запятой приводит к исключению недопустимой операции (SIGFPE). Если бы цикл, запускающий исключение, был оптимизирован, программное обеспечение больше не работало бы так же.
Однако, как указывает user2357112 в комментариях , стандарт C11 явно оставляет неопределенным поведение сигнализации NaNs ( sNaN), поэтому компилятору разрешено предполагать, что они не происходят, и, таким образом, исключения, которые они вызывают, также не возникают. Стандарт C ++ 11 опускает описание поведения для сигнализации NaN и, таким образом, также оставляет его неопределенным.
Режимы округления
В альтернативных режимах округления допустимые оптимизации могут измениться. Например, в режиме Round-to-Negative-Infinity оптимизация x+0.0 -> xстановится допустимой, ноx-0.0 -> x становится запрещенной.
Чтобы предотвратить использование GCC режимов округления и поведения по умолчанию, экспериментальный флаг -frounding-mathможно передать GCC.
Вывод
Clang и GCC , даже в -O3, остаются совместимыми с IEEE-754. Это означает, что он должен соответствовать вышеуказанным правилам стандарта IEEE-754. x+0.0это не бит идентичен с xдля всех в xсоответствии с этими правилами, но x*1.0 могут быть выбраны так : То есть, когда мы
- Соблюдайте рекомендацию передавать без изменений полезную нагрузку,
xесли это NaN.
- Оставьте знаковый бит результата NaN неизменным на
* 1.0 .
- Соблюдайте порядок XOR знакового бита во время частного / произведения, если
xэто не NaN.
Чтобы включить оптимизацию IEEE-754-unsafe (x+0.0) -> x, -ffast-mathнеобходимо передать флаг в Clang или GCC.