Сигнала 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 4

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;
}

Это исключение, очевидно, отключено по умолчанию, иначе вы вряд ли могли бы вообще выполнять вычисления с плавающей запятой.