Сигнала sigfpe шалостей
Я повторяю один и тот же расчет дважды, но в одном случае я получаю исключение с плавающей точкой, а в другом-нет.
#include <iostream>
#include <cmath>
#include <fenv.h>
using namespace std;
int main(void)
{
feenableexcept(-1);
double x,y,z;
x = 1.0;
y = (1.0/(24.3*24.0*3600.0))*x;
cout << "y = " << y << endl;
z = x/(24.3*24.0*3600.0);
cout << "z = " << z << endl;
return 0;
}
Я протестировал его как на g++, так и на clang++ и получил следующий результат в обоих
y = 4.76299e-07
Floating point exception
Что происходит?
2 ответа:
Проблема с
feenableexcept(-1);
Эти наборыисключений FPE для всех возможных случаев включают неточные результаты (которые очень распространены в операциях с плавающей запятой). Вы действительно не должны использовать число здесь, а вместо этого использовать предоставленные макросы для установки битов, которые вы хотите.
Замена на
feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW);
Решает эту проблему для меня.
Когда
feenableexcept(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT);
Дано, SIGFPE вернется. Это показывает, что FE_INEXACT является основной причиной проблемы.
Причина, по которой первое вычисление не дает SIGFPE, заключается в том, что разделение уже выполнено во время компиляции (с неточными результатами). Во время выполнения выполняется только умножение, что не вносит дополнительной неточности.
Это исключение FE_INEXACT.
Первая операция не вызывает этого исключения, потому что
Это означает, чтоx
, умноженное на константу1/(24.3*24.0*3600.0)
, вычисленную во время компиляции, не может быть преобразовано в double без потери точности.x
равно 1.0, которое имеет точное представление, и константа уже была преобразована в некоторое (неточное) двойное представление во время компиляции.Поскольку обработка исключений с плавающей запятой не стандартизирована, это может остаться незамеченным другие компиляторы / платформы.
#include <iostream> #include <cmath> #include <fenv.h> using namespace std; int main(void) { feenableexcept(FE_INEXACT); // comment this line out and the exception is gone double x,y,z; x = 1.0; y = (1.0/(24.3*24.0*3600.0))*x; cout << "y = " << y << endl; z = x/(24.3*24.0*3600.0); // <-- FE_INEXACT exception cout << "z = " << z << endl; return 0; }
Это исключение, очевидно, отключено по умолчанию, иначе вы вряд ли могли бы вообще выполнять вычисления с плавающей запятой.