Образец кода:
#include <iostream>
#include <cmath>
#include <stdint.h>
using namespace std;
static bool my_isnan(double val) {
union { double f; uint64_t x; } u = { val };
return (u.x << 1) > 0x7ff0000000000000u;
}
int main() {
cout << std::isinf(std::log(0.0)) << endl;
cout << std::isnan(std::sqrt(-1.0)) << endl;
cout << my_isnan(std::sqrt(-1.0)) << endl;
cout << __isnan(std::sqrt(-1.0)) << endl;
return 0;
}
С -ffast-math
, этот код печатает «0, 0, 1, 1» — без, он печатает «1, 1, 1, 1».
Это верно? я думал так std::isinf
/std::isnan
должен еще работать с -ffast-math
в этих случаях.
Кроме того, как я могу проверить бесконечность / NaN с -ffast-math
? Вы можете увидеть my_isnan
Делая это, и это на самом деле работает, но это решение, конечно, очень зависит от архитектуры. Кроме того, почему my_isnan
работать здесь и std::isnan
не? Как насчет __isnan
а также __isinf
, Они всегда работают?
С -ffast-math
что является результатом std::sqrt(-1.0)
а также std::log(0.0)
, Это становится неопределенным, или это должно быть NaN / -Inf?
Связанные обсуждения: (GCC) [Ошибка libstdc ++ / 50724] Новое: isnan прервано с помощью -ffinite-math-only в g ++, (Mozilla) Ошибка 416287 — возможность улучшения производительности с помощью isNaN
Обратите внимание, что -ffast-math
может заставить компилятор игнорировать / нарушать спецификации IEEE, см. http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-Options :
Эта опция не включена ни одной опцией -O, кроме -Ofast, так как
может привести к неправильному выводу для программ, которые зависят от точного
внедрение правил / спецификаций IEEE или ISO для математических функций.
Это может, однако, дать более быстрый код для программ, которые не требуют
гарантии этих спецификаций.
Таким образом, используя -ffast-math
вам не гарантировано видеть бесконечность там, где вы должны.
Особенно, -ffast-math
включается -ffinite-math-only
, увидеть http://gcc.gnu.org/wiki/FloatingPointMath что означает (от http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html#Optimize-Options )
[…] оптимизация для арифметики с плавающей точкой, которая предполагает, что аргументы и результаты не являются NaN или + -Infs
Это означает, что, позволяя -ffast-math
вы даете обещание компилятору, что ваш код никогда не будет использовать бесконечность или NaN, что в свою очередь позволяет компилятору оптимизировать код, например, заменяя любые вызовы isinf
или же isnan
по константе false
(и дальше оптимизировать оттуда). Если вы нарушаете свое обещание компилятору, компилятор не обязан создавать правильные программы.
Таким образом, ответ довольно прост, если ваш код может иметь бесконечности или NaN (что настоятельно подразумевается тем, что вы используете isinf
а также isnan
), вы не можете включить -ffast-math
иначе вы можете получить неправильный код.
Ваша реализация my_isnan
работает (в некоторых системах), потому что он напрямую проверяет двоичное представление числа с плавающей запятой. Конечно, процессор все еще может выполнять (некоторые) фактические вычисления (в зависимости от того, какие оптимизации выполняет компилятор), и, таким образом, фактические NaN могут появляться в памяти, и вы можете проверить их двоичное представление, но, как объяснено выше, std::isnan
возможно, был заменен константой false
, Также может случиться так, что компилятор заменит, например, sqrt
по какой-то версии, которая даже не производит NaN для ввода -1
, Чтобы увидеть, какие оптимизации выполняет ваш компилятор, скомпилируйте его на ассемблер и посмотрите на этот код.
Чтобы сделать (не полностью несвязанную) аналогию, если вы говорите своему компилятору, что ваш код находится на C ++, вы не можете ожидать, что он правильно скомпилирует код C и наоборот (для этого есть реальные примеры, например, Может ли код, действительный как на C, так и на C ++, вызывать различное поведение при компиляции на каждом языке? ).
Это плохая идея, чтобы включить -ffast-math
и использовать my_isnan
поскольку это сделает все очень зависимым от машины и компилятора, вы не знаете, какие оптимизации выполняет компилятор в целом, поэтому могут быть и другие скрытые проблемы, связанные с тем, что вы используете неконечные математические вычисления, но скажите компилятору иначе.
Простое исправление заключается в использовании -ffast-math -fno-finite-math-only
что все равно даст некоторые оптимизации.
Также возможно, что ваш код выглядит примерно так:
В этом случае вы можете разделить ваш код и использовать оптимизировать #pragma
или же __attribute__
превратить -ffast-math
(соответственно -ffinite-math-only
а также -fno-finite-math-only
) включать и выключать выборочно для заданных фрагментов кода (однако, я помню, что были некоторые проблемы с какой-то версией GCC, связанной с этим), или просто разбить ваш код на отдельные файлы и скомпилировать их с разными флагами. Конечно, это также работает в более общих настройках, если вы можете изолировать части, где могут возникнуть бесконечности и NaN. Если вы не можете изолировать эти части, это явный признак того, что вы не можете использовать -ffinite-math-only
для этого кода.
Наконец, важно понимать, что -ffast-math
это не безопасная оптимизация, которая просто делает вашу программу быстрее. Это влияет не только на производительность вашего кода, но и на его корректность (и это, помимо всего прочего, уже над всеми проблемами, касающимися чисел с плавающей запятой, если я правильно помню Уильям Кахан имеет коллекцию страшных историй на своей домашней странице, см. также Что должен знать каждый программист об арифметике с плавающей точкой). Короче говоря, вы можете получить более быстрый код, но также ошибочные или неожиданные результаты (пример приведен ниже). Следовательно, вы должны использовать такие оптимизации только тогда, когда вы действительно знаете, что делаете, и вы абсолютно уверены, что либо
Программный код может вести себя совершенно по-разному, в зависимости от того, используется эта оптимизация или нет. В частности, он может вести себя неправильно (или, по крайней мере, очень противоречит вашим ожиданиям), когда такие оптимизации, как -ffast-math
включены Возьмите следующую программу, например:
#include <iostream>
#include <limits>
int main() {
double d = 1.0;
double max = std::numeric_limits<double>::max();
d /= max;
d *= max;
std::cout << d << std::endl;
return 0;
}
будет производить продукцию 1
как и ожидалось при компиляции без какого-либо флага оптимизации, но с использованием -ffast-math
будет выводить 0
,
Других решений пока нет …