Pull to refresh

Что такое -1.#IND и -1.#J?

Reading time 3 min
Views 36K
Original author: Raymond Chen

Любой опытный программист знает, что стандарт представления значений с плавающей точкой (IEEE 754) оставляет несколько зарезервированных значений, соответствующих не-числам (NaN, not-a-number). Стандартная библиотека Visual C печатает не-числа следующим образом:
Печатается Означает
1.#INF Положительная бесконечность
-1.#INF Отрицательная бесконечность
1.#SNAN Положительное сигнальное не-число (signaling NaN)
-1.#SNAN Отрицательное сигнальное не-число (signaling NaN)
1.#QNAN Положительное несигнальное не-число (quiet NaN)
-1.#QNAN Отрицательное несигнальное не-число (quiet NaN)
1.#IND Положительная неопределённость
-1.#IND Отрицательная неопределённость
Положительная и отрицательная бесконечности могут получаться при переполнении в результате арифметического действия — например, при делении на ноль, или при взятии логарифма от положительного нуля. (По стандарту IEEE, любое значение с плавающей точкой имеет определённый знак — не только не-числа существуют в положительном и отрицательном вариантах, но и нулей тоже два.)

Сигнальные и несигнальные не-числа, как правило, получаются не в результате вычислений, а генерируются программой намеренно — например, в C++ их можно получить, вызвав методы signaling_NaN() или quiet_NaN() класса std::numeric_limits<T>. Скажем, воображаемая программа электронных таблиц могла бы использовать их для обозначения высокоуровневых ошибок при вычислении ячейки — «циклическая ссылка», «неизвестная функция» и т.п. Любая операция с использованием не-числа даёт в результате не-число, т.е. приложению достаточно проверить только окончательный результат вычисления, чтобы убедиться в корректности всех этапов.

Сигнальных и несигнальных не-чисел зарезервировано много, но runtime-библиотека Visual C при печати различает их только по знаку, и игнорирует присвоенное приложением значение. Исключениями являются две неопределённости, которые могут получаться при вычислении, не имеющем ни конечного, ни бесконечного результата: например, сумма 1.#INF и -1.#INF, логарифм или квадратный корень отрицательного числа. В стандарте прописано, что неопределённость должна представляться несигнальным не-числом, но каким именно — не определено. В разных процессорах значения получаются разными, так что разработчикам стандартных библиотек приходится экспериментировать. В x86 неопределённость получается отрицательным несигнальным не-числом с нулевым значением, но в других процессорах неопределённость может оказаться положительной.

(Прим. перев.: я не поленился и проверил, на x86 логарифм отрицательного нуля всё равно -1.#INF, как и у положительного — а не -1.#IND)

Комментаторы Чена обратили внимание, что не-числа могут печататься и иначе.
Например, нижеследующий код напечатает 1.#J:
   double z = 0;
   printf("%.2f", 1/z);

В результате деления на ноль получается 1.#INF, и затем runtime-библиотека должна округлить это не-число до двух знаков после запятой. Вероятно, именно поэтому все не-числа начинаются с "1." — чтобы при округлении можно было найти запятую, и отсчитывать от неё знаки. Отсчитываем два, получаем "#I", и следующая «цифра» — "N" — больше 5, так что мы округляем предыдущую «цифру» вверх до "J".

Графически это даже кажется понятным — что такое J, как не I, закруглённая вверх?

Вряд ли такая печать не-чисел была заложена в спецификации; но после того, как библиотека выпущена, менять алгоритм уже нельзя — одному богу известно, сколько существующих приложений полагаются на то, что получат именно 1.#J.

С другой стороны Реймонд замечает, что фонетическое округление (огубление) I давало бы Ü. Вот если бы округление 1.#INF до двух знаков печатало 1.#Ü, это было бы über-гикство.
Tags:
Hubs:
+28
Comments 5
Comments Comments 5

Articles