Как использовать 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);

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.  */

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.