Как использовать nan и inf в C?

89

У меня есть числовой метод, который может вернуть nan или inf, если произошла ошибка, и для целей тестирования я хотел бы временно заставить его возвращать nan или inf, чтобы убедиться, что ситуация обрабатывается правильно. Есть ли надежный, независимый от компилятора способ создания значений nan и inf в C?

После 10 минут поиска в Google я смог найти только решения, зависящие от компилятора.

Графика Нуб
источник
поплавки не определены стандартом C. Так что не существует независимого от компилятора способа делать то, что вы хотите.
Йохан Котлински

Ответы:

86

Вы можете проверить, есть ли это в вашей реализации:

#include <math.h>
#ifdef NAN
/* NAN is supported */
#endif
#ifdef INFINITY
/* INFINITY is supported */
#endif

Существование INFINITYгарантируется C99 (или, по крайней мере, последним черновиком) и «расширяется до постоянного выражения типа float, представляющего положительную или беззнаковую бесконечность, если доступно; в противном случае до положительной константы типа float, которая переполняется во время перевода».

NAN может быть определен или не определен, и «определяется тогда и только тогда, когда реализация поддерживает тихие NaN для типа float. Оно расширяется до константного выражения типа float, представляющего тихое NaN».

Обратите внимание, что если вы сравниваете значения с плавающей запятой, и выполните:

a = NAN;

даже тогда,

a == NAN;

ложно. Один из способов проверить NaN:

#include <math.h>
if (isnan(a)) { ... }

Вы также можете: a != aпроверить, aявляется ли NaN.

Существует также isfinite(), isinf(), isnormal()и signbit()макросы в math.hв C99.

C99 также имеет nanфункции:

#include <math.h>
double nan(const char *tagp);
float nanf(const char *tagp);
long double nanl(const char *tagp);

(Ссылка: n1256).

Документы INFINITY Docs NAN

Алок Сингхал
источник
2
Отличный ответ. Ссылка на макросы NAN и INFINITY - это C99 §7.12, параграфы 4 и 5. Кроме (isnan (a)), вы также можете проверить NaN, используя (a! = A) в соответствующей реализации C.
Стивен Канон,
23
За любовь к читаемости, a != aне должны НИКОГДА быть использованы.
Крис Керекес,
1
@ChrisKerekes: к сожалению, у некоторых из нас есть NAN, но нет isnan (). Да, это 2017 год. :(
eff
C не требует, когда aэто не-число, для a == NANвозврата false. IEEE требует этого. Даже реализации, которые придерживаются IEEE, в основном это делают . Если это isnan()не реализовано, все же лучше обернуть тест, чем напрямую писать код a == NAN.
chux - Восстановить Монику
34

Не существует независимого от компилятора способа сделать это, поскольку ни стандарты C (ни C ++) не говорят, что математические типы с плавающей запятой должны поддерживать NAN или INF.

Изменить: я только что проверил формулировку стандарта C ++, и в нем говорится, что эти функции (члены шаблонного класса numeric_limits):

quiet_NaN() 
signalling_NaN()

вернет представления NAN «если доступно». Он не расширяет то, что означает «если доступно», но, по-видимому, что-то вроде «если представитель реализации FP поддерживает их». Аналогично есть функция:

infinity() 

который возвращает положительное сообщение INF "если доступно".

Оба они определены в <limits>заголовке - я предполагаю, что в стандарте C есть что-то подобное (возможно, также «если доступно»), но у меня нет копии текущего стандарта C99.


источник
Это разочаровывает и удивляет. Разве C и C ++ не соответствуют числам с плавающей запятой IEEE, которые имеют стандартное представление для nan и inf?
Graphics Noob
14
В C99, заголовок C <math.h>Определяет nan(), nanf()и nanl()которые возвращают различные представления NaN (как double, float, и , intсоответственно), и бесконечности (если имеющеся) могут быть возвращены путем генерации один с log(0)или что - то. Нет стандартного способа их проверить, даже в C99. <float.h>Заголовок ( <limits.h>для целочисленных типов), к сожалению , умалчивает о infи nanценности.
Крис Лутц
Вау, это большая путаница. nanl()возвращает a long double, а не intкак в моем комментарии. Не знаю, почему я этого не осознавал, когда печатал.
Крис Лутц
@Chris, см. Мой ответ для C99.
Алок Сингхал,
2
@IngeHenriksen - Совершенно уверен, что Microsoft заявила, что не намерена поддерживать C99 в VC ++.
Крис Лутц
24

Это работает как floatи double:

double NAN = 0.0/0.0;
double POS_INF = 1.0 /0.0;
double NEG_INF = -1.0/0.0;

Изменить: как кто-то уже сказал, старый стандарт IEEE сказал, что такие значения должны вызывать ловушки. Но новые компиляторы почти всегда отключают ловушки и возвращают заданные значения, потому что перехват мешает обработке ошибок.

Торстен С.
источник
Треппинг был одним из вариантов обработки ошибок, разрешенных в 754–1985 гг. Также было разрешено поведение, используемое большинством современного оборудования / компиляторов (и было предпочтительным поведением для многих членов комитета). Многие разработчики ошибочно полагали, что перехват был необходим из-за неудачного использования термина «исключения» в стандарте. Это было значительно разъяснено в новой редакции 754-2008.
Стивен Кэнон
Привет, Стивен, вы правы, но в стандарте также сказано: «Пользователь должен иметь возможность запрашивать ловушку для любого из пяти исключений, указав для нее обработчик. Он должен иметь возможность запрашивать отключение существующего обработчика. , сохранен или восстановлен. Он также должен иметь возможность определить, включен ли конкретный обработчик прерываний для назначенного исключения ". «следует», как определено (2. Определения), означает «настоятельно рекомендуется», и его реализация должна быть исключена, только если архитектура и т. д. делает это непрактичным. 80x86 полностью поддерживает стандарт, поэтому у C нет причин не поддерживать его.
Thorsten S.
Я согласен, что C должен требовать 754 (2008) с плавающей запятой, но есть веские причины не делать этого; в частности, C используется во всех типах сред, кроме x86, включая встроенные устройства, не имеющие аппаратного обеспечения с плавающей запятой, и устройства обработки сигналов, в которых программисты даже не хотят использовать числа с плавающей запятой. Верно или ошибочно, но именно эти варианты использования объясняют большую инерцию в спецификации языка.
Стивен Кэнон
Я не знаю, почему главный ответ оказался там. Это не дает возможности произвести запрошенные значения. Этот ответ имеет значение.
drysdam
#define is_nan(x) ((x) != (x))может быть полезен как простой переносимый тест для NAN.
Боб Штайн
21

Независимый от компилятора способ, но не независимый от процессора способ получить это:

int inf = 0x7F800000;
return *(float*)&inf;

int nan = 0x7F800001;
return *(float*)&nan;

Это должно работать на любом процессоре, который использует формат с плавающей запятой IEEE 754 (что и делает x86).

ОБНОВЛЕНИЕ: Протестировано и обновлено.

Аарон
источник
2
@WaffleMatt - почему бы не использовать этот порт между 32/64 бит? IEEE 754 с плавающей точкой с одинарной точностью является 32-битным независимо от размера адресации базового процессора.
Аарон
6
Трансляция на (float &)? Мне это не похоже на C. Вам нужноint i = 0x7F800000; return *(float *)&i;
Крис Лутц
6
Обратите внимание, что 0x7f800001это так называемый сигнальный NaN в стандарте IEEE-754. Хотя большинство библиотек и оборудования не поддерживают сигнальные NaN, вероятно, лучше вернуть тихий NaN 0x7fc00000.
Стивен Кэнон
6
Предупреждение: это может вызвать Undefined Behavior из- за нарушения строгих правил псевдонима . Рекомендуемый (и лучше всего поддерживаемый в компиляторах) способ прокрутки типов - использование членов объединения .
ulidtko 06
2
В дополнение к проблеме строгого псевдонима, на которую указал @ulidtko, это предполагает, что цель использует тот же порядок байтов для целых чисел, что и с плавающей запятой, что определенно не всегда так.
mr.stobbe
15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);
J.Kraftcheck
источник
4
Это умное портативное решение! C99 требует strtodи преобразует NaN и Inf.
ulidtko 06
1
Не то чтобы это решение было недостатком; они не константы. Вы не можете использовать эти значения, например, для инициализации глобальной переменной (или для инициализации массива).
Marc
1
@Marc. У вас всегда может быть функция инициализатора, которая вызывает их один раз и устанавливает их в глобальном пространстве имен. Это вполне реальный недостаток.
Безумный физик,
3
<inf.h>

/* IEEE positive infinity.  */

#if __GNUC_PREREQ(3,3)
# define INFINITY   (__builtin_inff())
#else
# define INFINITY   HUGE_VALF
#endif

а также

<bits/nan.h>
#ifndef _MATH_H
# error "Never use <bits/nan.h> directly; include <math.h> instead."
#endif


/* IEEE Not A Number.  */

#if __GNUC_PREREQ(3,3)

# define NAN    (__builtin_nanf (""))

#elif defined __GNUC__

# define NAN \
  (__extension__                                  \
   ((union { unsigned __l __attribute__ ((__mode__ (__SI__))); float __d; })  \
    { __l: 0x7fc00000UL }).__d)

#else

# include <endian.h>

# if __BYTE_ORDER == __BIG_ENDIAN
#  define __nan_bytes       { 0x7f, 0xc0, 0, 0 }
# endif
# if __BYTE_ORDER == __LITTLE_ENDIAN
#  define __nan_bytes       { 0, 0, 0xc0, 0x7f }
# endif

static union { unsigned char __c[4]; float __d; } __nan_union
    __attribute_used__ = { __nan_bytes };
# define NAN    (__nan_union.__d)

#endif  /* GCC.  */
4pie0
источник
0

Я также удивлен, что это не константы времени компиляции. Но я полагаю, что вы могли бы достаточно легко создать эти значения, просто выполнив инструкцию, которая возвращает такой недопустимый результат. Деление на 0, логарифм 0, загар 90 и тому подобное.

Карл Смотрич
источник
0

Я обычно использую

#define INFINITY (1e999)

или

const double INFINITY = 1e999

который работает, по крайней мере, в контекстах IEEE 754, потому что самое высокое представимое двойное значение примерно равно 1e308. 1e309будет работать так же хорошо, как и если бы 1e99999, но трех девяток достаточно и запоминается. Поскольку это либо двойной литерал (в данном #defineслучае), либо фактическое Infзначение, оно останется бесконечным, даже если вы используете 128-битные («длинные двойные») числа с плавающей запятой.

Дуглас Бэгнолл
источник
1
На мой взгляд, это очень опасно. Представьте, как кто-то переводит ваш код на 128-битные числа с плавающей запятой примерно через 20 лет (после того, как ваш код пройдет невероятно сложную эволюцию, ни один из этапов которого вы не смогли предсказать сегодня). Внезапно диапазон экспоненты резко увеличивается, и все ваши 1e999литералы больше не округляются до +Infinity. По законам Мерфи это нарушает алгоритм. Хуже того: человек-программист, выполняющий «128-битную» сборку, вряд ли заметит эту ошибку заранее. Т.е. скорее всего будет поздно, когда эта ошибка будет обнаружена и распознана. Очень опасно.
ulidtko 06
1
Конечно, описанный выше худший сценарий может быть далек от реальности. Но все же рассмотрите альтернативы! Лучше перестраховаться.
ulidtko 06
2
«Через 20 лет», хе. Давай. Ответ на этот вопрос не является , что плохо.
alecov
@ulidtko Мне это тоже не нравится, а правда?
Ихароб Аль-Асими,
0

Вот простой способ определения этих констант, и я уверен, что он переносимый:

const double inf = 1.0/0.0;
const double nan = 0.0/0.0;

Когда я запускаю этот код:

printf("inf  = %f\n", inf);
printf("-inf = %f\n", -inf);
printf("nan  = %f\n", nan);
printf("-nan = %f\n", -nan);

Я получил:

inf  = inf
-inf = -inf
nan  = -nan
-nan = nan
Патрик Чкорефф
источник