Это хорошая идея использовать IEEE754 с плавающей запятой NaN для значений, которые не установлены?

Является ли хорошей идеей использовать NaN с плавающей запятой IEEE754 (не число) для значений, которые не определены по нематематическим причинам?

В нашем случае они еще не установлены, потому что значения не были получены с какого-то другого устройства. Контекст представляет собой встроенную систему, использующую значения IEC1131 REAL32. Редактировать: язык программирования — C, поэтому мы, скорее всего, будем использовать NAN и isnanf(x), которые появились в C99. Хотя нам могут понадобиться некоторые дополнительные искажения, чтобы добавить их в наш уровень совместимости с ОС.

По умолчанию в языках программирования, по-видимому, переменные с плавающей запятой инициализируются положительным нулем, внутреннее представление которого — все нули. Это непригодно для нас, потому что 0 находится в диапазоне допустимых значений.

Использование NaN кажется чистым решением, но, может быть, это больше хлопот, чем оно того стоит, и нам следует выбрать какое-то другое значение?


person starblue    schedule 24.06.2009    source источник
comment
Я не знаю C, но в .NET 1.1 до того, как появились типы, допускающие значение NULL, многие люди использовали такие вещи, как минимальное значение (int.MinValue). Проблема в том, что вы должны учитывать это везде и гарантировать, что вы никогда не используете MinValue. Возможно, что-то подобное существует для C?   -  person RichardOD    schedule 24.06.2009
comment
Я задал аналогичный (но не тот же) вопрос, может быть, ответы помогут и вам. stackoverflow.com/questions/787828/nan-as-a -специальный-аргумент   -  person quinmars    schedule 24.06.2009


Ответы (9)


Только что заметил этот вопрос.

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

Тем не менее, есть несколько подводных камней, которые находятся вне контроля комитета 754: как отмечали другие, не все оборудование поддерживает значения NaN на скорости, что может привести к снижению производительности. К счастью, нечасто приходится выполнять множество операций с инициализированными данными в критически важных для производительности условиях.

person Stephen Canon    schedule 15.10.2009
comment
Принято, потому что в этом случае мы использовали NaN для undefined, хотя это оказалось более хлопотным, чем ожидалось. В основном это было связано с тем, что поддержка NaN в наших инструментах и ​​системах отсутствовала или содержала ошибки, и нам приходилось обходить это. - person starblue; 16.12.2010

NaN — разумный выбор для предложения «нет значения» (например, язык программирования D использует их для неинициализированных значений), но поскольку любые сравнения с их участием будут ложными, вы можете получить несколько сюрпризов:

  • if (result == DEFAULT_VALUE) не будет работать должным образом, если DEFAULT_VALUE равно NaN, как упоминал Джон.

  • Они также могут вызвать проблемы с проверкой диапазона, если вы не будете осторожны. Рассмотрим функцию:

bool isOutsideRange(double x, double minValue, double maxValue)
{
    return x < minValue || x > maxValue;
}

Если x равно NaN, эта функция неправильно сообщит, что x находится между minValue и maxValue.

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

[Редактировать: изначально мне удалось напечатать выше «любые сравнения с их участием будут верными», что я имел в виду не так, и это неправильно, все они ложны, кроме NaN != NaN, что верно]

person jskinner    schedule 24.06.2009
comment
В каком языке используются эти правила сравнения? Может быть, Д делает. Но по крайней мере C и C++ не работают с NaN таким образом. Все сравнения порядка будут ложными. x == NaN ложно для любого x, включая NaN. - person Igor Krivokon; 24.06.2009
comment
Нет, ваша функция только сообщает, что она не выходит за пределы диапазона. Это ни внутри, ни снаружи, что действительно может сбить с толку тех, кто наивно использует числа с плавающей запятой. - person starblue; 24.06.2009
comment
@Igor: Мы говорим то же самое здесь. isOutsideRange вернет false, если x равно NaN, что означает, что он находится внутри диапазона, а это не так. - person jskinner; 24.06.2009
comment
@jskinner Нет, это не означает, что он находится внутри диапазона. По сути, NaN нигде нет. - person starblue; 24.06.2009
comment
@starblue: я понимаю это. 'isOutsideRange' является примером плохо определенной функции перед лицом входных данных NaN: числа NaN не находятся ни внутри диапазона, ни за его пределами, поэтому возвращать логическое значение неуместно. Это просто пример того, как то, что выглядит хорошо на поверхности, на самом деле оказывается неверным, когда вводятся NaN. - person jskinner; 24.06.2009
comment
IEEE необходимо добавить NaB. Сравнение даст true, false или NaB. Любые определения логического значения, не учитывающие NaB, будут опубликованы на thedailywtf. - person Windows programmer; 26.06.2009
comment
Кроме того, sort, вероятно, является наивным пользователем входных данных с плавающей запятой в том смысле, что если вы сортируете массив с плавающей запятой, любые значения NaN могут привести к неправильной сортировке даже остальных значений. Например. в Python sorted([1,2,3,float('nan'),1,2,3]) возвращает [1,2,3,nan,1,2,3], а в Clojure (sort [1 2 3 (Float. NaN) 1 2 3]) возвращает (1 2 3 NaN 1 2 3). - person Jouni K. Seppänen; 19.12.2009

Я использовал NaN в подобных ситуациях только из-за этого: обычное значение инициализации по умолчанию 0 также является допустимым значением. NaN пока работают нормально.

Кстати, хороший вопрос, почему значение инициализации по умолчанию обычно (например, в примитивных типах Java) равно 0, а не NaN. А не может ли быть 42 или что-то в этом роде? Интересно, в чем смысл нулей.

person Joonas Pulakka    schedule 24.06.2009
comment
Я думаю, что причина использования 0 заключается в том, что память инициализируется нулевыми байтами независимо от типа, например, в сегменте BSS C. - person starblue; 24.06.2009
comment
Ага, наверное, что-то в этом роде. Но теперь, когда разработчики языка/компилятора приложили усилия для инициализации памяти, не будет ли почти так же просто инициализировать любое произвольное значение (кроме нуля)? Нули - это просто биты среди прочих :-) - person Joonas Pulakka; 24.06.2009
comment
@mad-j: вы хотите инициализировать всю память с одним и тем же битовым шаблоном. Таким образом, это не может быть 42, потому что тогда вам обычно придется делать что-то другое для двух соседних шорт, чем то, что вы делаете для целого числа. Это оставляет 0 и -1. Но 0xffffffff не равно -1 как число с плавающей запятой, поэтому у вас будет несоответствие. В нем не так много, но я думаю, что 0, вероятно, лучше всего. Кроме того, некоторые аппаратные средства могут одновременно эффективно обнулять целые блоки физической памяти, чего бы это ни стоило. - person Steve Jessop; 24.06.2009

Я думаю, что это плохая идея в целом. Следует иметь в виду, что большинство ЦП обрабатывают Nan намного медленнее, чем «обычное» плавание. И трудно гарантировать, что у вас никогда не будет Нэн в обычных условиях. Мой опыт в численных вычислениях показывает, что они часто приносят больше проблем, чем пользы.

Правильное решение — не кодировать «отсутствие значения» в float, а сигнализировать об этом другим способом. Однако это не всегда практично, в зависимости от вашей кодовой базы.

person David Cournapeau    schedule 24.06.2009

Будьте осторожны с NaN... они могут распространяться как лесной пожар, если вы не будете осторожны.

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

D использует это как обоснование для присвоения поплавкам NaN по умолчанию. (С чем я не уверен, что согласен.)

person Chris Burt-Brown    schedule 24.06.2009
comment
Эээ... Разве смысл NaN не в том, что они будут распространяться? Гораздо лучше иметь в результате NaN, что указывает на то, что что-то не так, чем иметь невинно выглядящее, но совершенно неправильное число (которое возникнет в результате случайного использования чисел, инициализированных нулем). - person Joonas Pulakka; 24.06.2009
comment
И да, и нет, потому что, когда вы обнаруживаете NaN, только просматривая вывод или явно проверяя наличие NaN. Следствием этого является то, что ошибки могут быть обнаружены намного позже, чем они возникли. С другой стороны, если вы используете NULL (если возможно), вы довольно быстро получаете ошибку NPE/segmentation. Жестокий, но действенный. - person quant_dev; 11.07.2009
comment
Если все, что вы когда-либо знали, это то, что NaN повсюду, это точно не поможет вам выяснить, откуда они берутся. - person corsiKa; 28.03.2011

Я чувствую, что это немного хакерски, но, по крайней мере, любые другие числа, которые вы выполняете с этим значением NaN, дают результат NaN - когда вы видите NaN в отчете об ошибке, вы, по крайней мере, знаете, какую ошибку вы ищете.

person Szundi    schedule 24.06.2009

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

Просто помните, что в зависимости от вашей среды вам, вероятно, понадобится особый способ обнаружения NaN (не используйте просто if (x == float.NaN) или что-то подобное).

person Jon Skeet    schedule 24.06.2009
comment
Не верьте этому ответу. Все, что нужно сделать Джону Скиту, — это подумать о переменной, и она сама себя определит. - person Windows programmer; 24.06.2009
comment
Значение определяется перед Skeet вещами имени переменной, верно? - person glasnt; 24.06.2009

Это звучит как хорошее применение для бабушек для меня. Хотел бы я подумать об этом ...

Конечно, они должны распространяться как вирус, вот в чем дело.

Я думаю, что я бы использовал nan вместо одной из бесконечностей. Было бы неплохо использовать сигнальный nan и заставить его вызывать событие при первом использовании, но к тому времени уже слишком поздно, он должен замолчать при первом использовании.

person old_timer    schedule 10.07.2009

Использование NaN в качестве значения по умолчанию разумно.

Обратите внимание, что некоторые выражения, такие как (0,0/0,0), возвращают NaN.

person Joe Erickson    schedule 10.07.2009