Любой опытный программист знает, что стандарт представления значений с плавающей точкой (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 |
Отрицательная неопределённость |
Сигнальные и несигнальные не-числа, как правило, получаются не в результате вычислений, а генерируются программой намеренно — например, в 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-гикство.