Не число
Oct 8, 2017 · CommentsПрограммирование
Числа с плавающей запятой обманчиво привычны и из-за этого гораздо более коварны, чем кажется. Общеизвестно, что результат вычислений с плавающей запятой нельзя сравнивать на точное равенство - из-за погрешности вычислений совпадение двух величин с точностью до бита маловероятен. Но это только самая вершина айсберга. На точность влияет порядок вычислений, используемая платформа, математическая библиотека, процессор и т.д.
Сегодня я хотел бы поговорить о такой особенности чисел с плавающей запятой, как
“не число”. Он же NaN
(not a number). NaN
- это специальное значение, которое
возвращается как результат некоторых операций - например результат деления ноля
на ноль.
В двоичной форме NaN
представляется как число с ненулевой мантиссой и
максимальным показателем степени. Соответственно для числа одинарной точности
существует 16777214 способа записать NaN
в двоичном виде (23 битная мантисса).
Вторая особенность - любое сравнение с NaN
возвращает false
, в том числе
сравнение NaN
c собой. Эта особенность означает, к примеру, что числа с
плавающей запятой нельзя в общем случае использовать в качестве ключа. В
частности сортировка массива чисел с плавающей запятой не работает. Как вы
думаете, сколько программ в мире рассчитывает на то, что такая сортировка
работает?
Арифметические операции с NaN
также, как правило, возвращают NaN
. Это
приводит к так называемому “NaN
отравлению” - стоит только пропустить NaN
во входных данных, как на выходе тоже появляются сплошные NaN
. Соответственно,
все входные параметры нужно обязательно проверять на корректность.
Далее, существует две разновидности NaN
:
- signaling
NaN
: сигнальное не число,SNaN
. - quiet
NaN
: тихое не число,QNaN
.
В двоичном представлении SNaN
и QNaN
отличаются старшим битом мантиссы.
Единичный старший бит мантиссы означает QNaN
.
Поведение SNaN
и QNaN
отличается. Использование SNaN
в любой операции с
плавающей запятой приводит к исключению (AKA floating point
expection), в случае, если они разрешены. В большинстве случаев исключения
запрещены (так как их никто не обрабатывает все равно :-) ). В этом случае
процессор тихо превращает SNaN
в QNaN
. Это в свою очередь нарушает еще
одно допущение, которое делают программисты. А именно, что преобразование числа
одинарной точности в число двойной точности сохраняет все биты исходного числа.
Так как такое преобразование выполняется с помощью FPU, последний превращает
SNaN
на входе в QNaN
на выходе. Можете само проверить:
union {
float f;
uint32_t u;
} input, output;
// input.f = SNaN
input.u = 0x7f800001;
// d = QNaN
const volatile double d = input.f;
// output.f = QNaN
output.f = static_cast<float>(d);
if (input.u != output.u) {
printf("Oops.\n");
}
В общем хорошо, что 90% обычного кода - это вызовы функций и memcpy()
разного
вида. Иначе бы эксперты по безопасности вообще спать перестали бы.
Для поднятия настроения цитата с https://vk.com/spacex (орфография оригинала сохранена):
Falcon 9 и Iridium-3 установлены на SLC-4E в космодроме Vandenberg. Прогноз погоды на 90% благоприятный.
Почему “в” касмодроме,а не “на”????? Украина посторалась что ли