Як використовувати nan та inf у C?


89

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

Прогугливши близько 10 хвилин, я зміг знайти лише рішення, залежні від компілятора.


поплавки не визначені стандартом 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перевірити, чи aNaN.

Існує також 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

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

Редагувати: Я щойно перевірив формулювання стандарту C ++, і там сказано, що ці функції (члени шаблонованого класу numeric_limits):

quiet_NaN() 
signalling_NaN()

поверне подання NAN "за наявності". Це не розширює те, що означає "якщо є", але, мабуть, щось на кшталт "якщо представник FP реалізації підтримує їх". Аналогічно існує функція:

infinity() 

який повертає позитивне повторення INF "за наявності".

Вони обидва визначені в <limits>заголовку - я гадаю, що стандарт C має щось подібне (можливо, також "за наявності"), але у мене немає копії поточного стандарту C99.


Це розчаровує та дивує. Чи не відповідають C і C ++ числам з плаваючою комою IEEE, які мають стандартне представлення для nan та inf?
Графіка 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.
Alok Singhal

2
@IngeHenriksen - Майже впевнений, що Microsoft заявила, що не має наміру підтримувати VC ++ C99.
Chris Lutz,

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 не підтримував його.
Торстен С.

Я погоджуюсь, що C повинен вимагати 754 (2008) з плаваючою комою, але є вагомі причини, щоб це не робити; зокрема, C використовується у всіх типах середовищ, крім x86, включаючи вбудовані пристрої, які не мають апаратної плаваючої крапки, та пристрої обробки сигналів, де програмісти навіть не хочуть використовувати плаваючу крапку. Справедливо чи неправильно, ці вживання спричиняють велику інерцію в специфікації мови.
Стівен Канон

Я не знаю, чому найвища відповідь зробила це там. Це не дає жодного способу отримати запитувані значення. Ця відповідь відповідає.
Drysdam

#define is_nan(x) ((x) != (x))може бути корисним як простий портативний тест для NAN.
Bob Stein

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 &)? Мені це не схоже на С. Вам потрібноint i = 0x7F800000; return *(float *)&i;
Кріс Луц

6
Зверніть увагу, що 0x7f800001це так званий NaN сигналізації в стандарті IEEE-754. Хоча більшість бібліотек та обладнання не підтримують сигналізацію NaN, швидше за все, повернути тихий NaN, як 0x7fc00000.
Стівен Канон

6
Попередження: це може спричинити невизначену поведінку через порушення суворих правил псевдонімів . Рекомендований (і найкраще підтримуваний у компіляторах) спосіб виконання типових покарань - це члени профспілки .
ulidtko

2
На додаток до суворої проблеми псевдонімів, на яку вказав @ulidtko, це передбачає, що ціль використовує той самий ендіан для цілих чисел, що і з плаваючою комою, що, безумовно, не завжди має місце.
mr.stobbe

15
double a_nan = strtod("NaN", NULL);
double a_inf = strtod("Inf", NULL);

4
Це розумне портативне рішення! C99 вимагає strtodі перетворює NaN та Inf.
ulidtko

1
Не те, що у цього рішення є недолік; вони не є постійними. Ви не можете використовувати ці значення для ініціалізації глобальної змінної, наприклад (або для ініціалізації масиву).
Марк

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

1
Звичайно, найгірший сценарій, описаний вище, може бути далеко не реалістичним. Але все ж розгляньте альтернативи! Краще залишатися на безпеці.
ulidtko

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.