Стандарт 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-to-Near, Ties-to Even) ми бачимо, що x+0.0
виробляє x
, EXCEPT, коли 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, і, таким чином, також залишає її невизначеною.
Режими округлення
У альтернативних режимах округлення допустимі оптимізації можуть змінюватися. Наприклад, у режимі « Круглого до негативного» - нескінченність оптимізація 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-небезпечної оптимізації (x+0.0) -> x
прапор -ffast-math
потрібно передати Clang або GCC.