Не число

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

Сегодня я хотел бы поговорить о такой особенности чисел с плавающей запятой, как “не число”. Он же NaN (not a number). NaN - это специальное значение, которое возвращается как результат некоторых операций - например результат деления ноля на ноль.

В двоичной форме NaN представляется как число с ненулевой мантиссой и максимальным показателем степени. Соответственно для числа одинарной точности существует 16777214 способа записать NaN в двоичном виде (23 битная мантисса).

Вторая особенность - любое сравнение с NaN возвращает false, в том числе сравнение NaN c собой. Эта особенность означает, к примеру, что числа с плавающей запятой нельзя в общем случае использовать в качестве ключа. В частности сортировка массива чисел с плавающей запятой не работает. Как вы думаете, сколько программ в мире рассчитывает на то, что такая сортировка работает?

Арифметические операции с NaN также, как правило, возвращают NaN. Это приводит к так называемому “NaN отравлению” - стоит только пропустить NaN во входных данных, как на выходе тоже появляются сплошные NaN. Соответственно, все входные параметры нужно обязательно проверять на корректность.

Далее, существует две разновидности NaN:

В двоичном представлении 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% благоприятный.

Почему “в” касмодроме,а не “на”????? Украина посторалась что ли

comments powered by Disqus