std::isinf не работает с -ffast-math. как проверить на бесконечность
Образец кода:
#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
1 ответ
Обратите внимание, что -ffast-math
может заставить компилятор игнорировать / нарушать спецификации IEEE, см. http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Optimize-Options.html:
Эта опция не включена ни одной опцией -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)
[...] оптимизации для арифметики с плавающей точкой, предполагающие, что аргументы и результаты не являются NaN или +-Infs
Это означает, что, позволяя -ffast-math
вы даете обещание компилятору, что ваш код никогда не будет использовать бесконечность или NaN, что, в свою очередь, позволяет компилятору оптимизировать код, например, заменяя любые вызовы isinf
или же isnan
по константе false
(и дальше оптимизировать оттуда). Если вы нарушаете свое обещание компилятору, компилятору не требуется создавать правильные программы.
Таким образом, ответ довольно прост, если ваш код может иметь бесконечности или NaN (что настоятельно подразумевается тем, что вы используете isinf
а также isnan
), вы не можете включить -ffast-math
иначе вы можете получить неправильный код.
Ваша реализация my_isnan
работает (на некоторых системах), потому что он напрямую проверяет двоичное представление числа с плавающей запятой. Конечно, процессор все еще может выполнять (некоторые) фактические вычисления (в зависимости от того, какие оптимизации выполняет компилятор), и, таким образом, фактические NaN могут появляться в памяти, и вы можете проверить их двоичное представление, но, как объяснено выше, std::isnan
возможно, был заменен константой false
, Также может случиться так, что компилятор заменит, например, sqrt
по какой-то версии, которая даже не производит NaN для ввода -1
, Чтобы увидеть, какие оптимизации выполняет ваш компилятор, скомпилируйте его на ассемблер и посмотрите на этот код.
Чтобы сделать (не полностью несвязанную) аналогию, если вы говорите компилятору, что ваш код находится на C++, вы не можете ожидать, что он правильно скомпилирует код C, и наоборот (для этого есть реальные примеры, например Can-код, который является допустимым). в обоих C и C++ производят различное поведение при компиляции на каждом языке?).
Это плохая идея, чтобы включить -ffast-math
и использовать my_isnan
поскольку это сделает все очень зависимым от машины и компилятора, вы не знаете, какую оптимизацию выполняет компилятор в целом, поэтому могут быть и другие скрытые проблемы, связанные с тем фактом, что вы используете не конечную математику, но скажите компилятору иначе.
Простое исправление заключается в использовании -ffast-math -fno-finite-math-only
что все равно даст некоторые оптимизации.
Также возможно, что ваш код выглядит примерно так:
- отфильтровать все бесконечности и NaNs
- выполнить некоторые математические расчеты с отфильтрованными значениями (под этим я подразумеваю математические операции, которые гарантированно никогда не создадут бесконечности или NaN, это должно быть очень, очень тщательно проверено)
В этом случае вы можете разделить ваш код и использовать оптимизировать #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
,