Любой опытный программист знает, что стандарт представления значений с плавающей точкой (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-гикство.
Автор: tyomitch