Как люди проверяют nan и inf в C89 - PullRequest
7 голосов
/ 18 января 2020

isnan(), isinf() не было в спецификации c до C99, есть ли способ реализовать такую ​​функцию в C89?

Я мог бы использовать if (d * 0 != 0), чтобы проверить, был ли d NaN или Inf, но мы всегда компилируем наш проект с параметром -Werror=float-equal, который хотел бы выкрикнуть: error: no == or != on float point value

Как люди проверяют nan и inf в C89 тогда?

Ответы [ 3 ]

6 голосов
/ 18 января 2020

Поскольку C99

isinf(x) и isnan(x) работают с типом real-float типа x и не определены для других типов, таких как int. isinf(x) и isnan(x) являются макросами.

isinf(x) и isnan(x) используют одно и то же имя для float, double, long double и поэтому действуют как перегруженная функция.

isinf

Для C89 мы могли бы использовать отдельные функции для проверки на _MAX. Обратите внимание, что C89 не определяет long double.

A "roll your own" isinf() можно использовать ниже.

#include <float.h>
int isinf_f(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int isinf_d(double x) { return x < -DBL_MAX || x > DBL_MAX; }

Обратите внимание, что C не требует, чтобы реализация поддерживала infinity . Если так, то вышеупомянутое никогда не соответствует действительности.

isnan

"Свернуть" для C89 isnan() сложнее. Может быть сделано ниже как функции или просто макрос. Функциональность опирается на современное поведение «не число», где Нан никогда не равен ничему, даже самому себе. Значение не указано C89, но обычно за ним следует система с плавающей запятой. В противном случае вам потребуется более конкретный подход c для платформы.

/* Note `x` used twice here - so use with caution */
#define my_is_nan(x) ( (x) != (x) )

int isnan_f(float x) { x != x; }
int isnan_d(double x) { x != x; }

Обратите внимание, что C не требует, чтобы реализация поддерживала Not-a-number . Если это так, то вышеупомянутое никогда не соответствует действительности.

Учитывая эпоху C89 на диком и диком западе, я бы не согласился с IEEE 754. Infinity и NAN являются точными границами любых реализаций с плавающей запятой, которые не имеют формального соответствия. Удачи.


C позволяет использовать более широкую математику FP в зависимости от FLT_EVAL_METHOD, поэтому 1.0f / 7.0f может использовать double. Это немного усложняет ситуацию, но использование истинной функции приводит x выражение к желаемому типу.

1 голос
/ 18 января 2020

Если ваша система использует Стандарт IEEE 754 для представления значений с плавающей запятой (как это делают большинство), то вы можете явно проверить значения NaN и inf. 32-битные значения одинарной точности (32-битные) имеют 1 знаковый бит (бит 31), 8 bit показатель (биты 30-23) и 23 bit мантисса (биты 22-0), выложенные так (в двоичном формате):

SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM

Значение бесконечность обозначено показателем степени всех 1 и мантисса всех 0 (бит знака различает отрицательную бесконечность и положительную бесконечность).

A NaN значение обозначается показателем степени всех 1 и ненулевой дробью ( quiet NaN имеет самый значительный бит мантиссы , установленный , в то время как сигнализирующий Nan имеет этот бит очищенный).

Итак, путем «литья» 'float в 32-разрядное целое число без знака, мы можем явно проверить эти представления (предполагая, что unsigned int является 32-разрядным типом):

int IsInfinite(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) == 0 );
}

int NotANumber(float test) {
    unsigned int mask = *(unsigned int *)(&test);
    return ( (mask & 0x7F800000) == 0x7F800000 && (mask & 0x007FFFFF) != 0 );
}

Представление для значений двойной точности аналогично, но с 11 битами экспоненты (62-52) и 52 битами мантиссы (51-0).

0 голосов
/ 30 января 2020

Я хотел бы попытаться выставить NaN путем логического рассмотрения.

Если float равно , не бесконечно (ни больше, чем FLT_MAX, ни меньше чем -FLT_MAX), поэтому оно должно быть меньше или равно FLT_MAX. Если нет, то это NaN .

int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

Мой самый первый ответ, и он уже проголосовал. Поэтому я решил добавить пример. Ну, это работает, как и ожидалось.

#include <stdio.h>
#include <math.h>
#include <float.h>

static int is_inf(float x) { return x < -FLT_MAX || x > FLT_MAX; }
static int is_nan(float x) { return !is_inf(x) && !(x <= FLT_MAX); }

static void show(float f)
{
  float g = -f;
  printf("% f : %i %i %i %i\n", f, isinf(f), isnan(f), is_inf(f), is_nan(f));
  printf("% f : %i %i %i %i\n", g, isinf(g), isnan(g), is_inf(g), is_nan(g));
}

int main(void)
{
  float inf = FLT_MAX * 2.0f;
  float nan = inf / inf;

  show(inf);
  show(nan);
  show(FLT_MAX);
  show(FLT_MIN);
  show(0.0f);
  show(1234.5678f);

  return 0;
}

Скомпилировано и запущено в Linux:

$ gcc infnan.c
$ ./a.out
 inf : 1 0 1 0
-inf : -1 0 1 0
-nan : 0 1 0 1
 nan : 0 1 0 1
 340282346638528859811704183484516925440.000000 : 0 0 0 0
-340282346638528859811704183484516925440.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 0.000000 : 0 0 0 0
-0.000000 : 0 0 0 0
 1234.567749 : 0 0 0 0
-1234.567749 : 0 0 0 0

Дополнительное решение для типа данных double с улучшенным возвращаемым значением.

static int is_inf(double x) {
  if (x >  DBL_MAX) return  1;
  if (x < -DBL_MAX) return -1;
  return 0;
}
static int is_nan(double x) { return !is_inf(x) && !(x <= DBL_MAX); }

Подумав еще раз, is_nan можно вычислить проще.

int is_nan(double x) { return !(x > DBL_MAX || x <= DBL_MAX); }

Или даже без включенной константы.

int is_nan(double x) { return !(x > 0.0 || x <= 0.0); }
...