Стандарт 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.