Когда операция приводит к тихому NaN, нет никаких признаков того, что что-то необычное, пока программа не проверит результат и не увидит NaN. То есть вычисления продолжаются без какого-либо сигнала от блока с плавающей запятой (FPU) или библиотеки, если с плавающей запятой реализовано программное обеспечение. Сигнальный NaN будет генерировать сигнал, обычно в виде исключения из FPU. Будет ли выброшено исключение, зависит от состояния FPU.
C ++ 11 добавляет несколько языковых элементов управления средой с плавающей запятой и предоставляет стандартизированные способы создания и тестирования NaN . Однако то, реализованы ли элементы управления, недостаточно стандартизировано, и исключения с плавающей запятой обычно не перехватываются так же, как стандартные исключения C ++.
В системах POSIX / Unix исключения с плавающей запятой обычно перехватываются с помощью обработчика для SIGFPE .
Как qNaN и sNaN выглядят экспериментально?
Давайте сначала узнаем, как определить, есть ли у нас sNaN или qNaN.
Я буду использовать C ++ в этом ответе вместо C , потому что он предлагает удобный
std::numeric_limits::quiet_NaN
иstd::numeric_limits::signaling_NaN
который я не мог найти в C удобно.Однако мне не удалось найти функцию для классификации, является ли NaN sNaN или qNaN, поэтому давайте просто распечатаем необработанные байты NaN:
main.cpp
Скомпилируйте и запустите:
вывод на моей машине x86_64:
Мы также можем выполнить программу на aarch64 в пользовательском режиме QEMU:
и это дает точно такой же результат, что говорит о том, что несколько архитектур тесно реализуют IEEE 754.
На этом этапе, если вы не знакомы со структурой чисел с плавающей запятой IEEE 754, взгляните на: Что такое субнормальное число с плавающей запятой?
В двоичном формате некоторые из приведенных выше значений:
Из этого эксперимента мы видим, что:
qNaN и sNaN, похоже, различаются только битом 22: 1 означает тихо, а 0 означает сигнализацию
бесконечности также очень похожи с экспонентой == 0xFF, но имеют дробь == 0.
По этой причине NaN должны установить бит 21 в 1, иначе было бы невозможно отличить sNaN от положительной бесконечности!
nanf()
производит несколько разных NaN, поэтому должно быть несколько возможных кодировок:Так как
nan0
это то же самоеstd::numeric_limits<float>::quiet_NaN()
, мы делаем вывод, что все они разные тихие NaN.В C11 N1570 проект стандарта подтверждает , что
nanf()
порождает тихими пренебрежимо малых, потому чтоnanf
вперед кstrtod
и 7.22.1.3 «The strtod, strtof и strtold функций» , говорит:Смотрите также:
Как qNaN и sNaN выглядят в руководствах?
IEEE 754 2008 рекомендует (обязательное или необязательное TODO?):
но, похоже, не сказано, какой бит предпочтительнее для отличия бесконечности от NaN.
6.2.1 «Кодировки NaN в двоичных форматах» говорит:
Руководство разработчика программного обеспечения для архитектур Intel 64 и IA-32 - Том 1 Базовая архитектура - 253665-056RU Сентябрь 2015 4.8.3.4 «NaNs» подтверждает, что x86 следует IEEE 754, различая NaN и sNaN по старшему дробному разряду:
а также Справочное руководство по архитектуре ARM - ARMv8, для профиля архитектуры ARMv8-A - DDI 0487C.a A1.4.3 «Формат с плавающей запятой одинарной точности»:
fraction != 0
: Значение - NaN, либо тихое NaN, либо сигнальное NaN. Два типа NaN различаются битом старшей дроби, битом [22]:bit[22] == 0
: NaN - это сигнальный NaN. Знаковый бит может принимать любое значение, а оставшиеся дробные биты могут принимать любое значение, кроме всех нулей.bit[22] == 1
: NaN - это тихий NaN. Знаковый бит и оставшиеся дробные биты могут принимать любое значение.Как генерируются qNanS и sNaN?
Одно из основных различий между qNaN и sNaN заключается в следующем:
std::numeric_limits::signaling_NaN
Я не смог найти четких цитат из IEEE 754 или C11 для этого, но я также не могу найти встроенную операцию, которая генерирует sNaN ;-)
Однако в руководстве Intel этот принцип четко изложен в пункте 4.8.3.4 «NaN»:
Это видно из нашего примера, где оба:
производят точно такие же биты, как
std::numeric_limits<float>::quiet_NaN()
.Обе эти операции компилируются в одну инструкцию сборки x86, которая генерирует qNaN непосредственно в оборудовании (TODO подтверждается GDB).
Что по-разному делают сети qNaN и SNAN?
Теперь, когда мы знаем, как выглядят qNaN и sNaN и как ими манипулировать, мы, наконец, готовы попытаться заставить sNaN делать свое дело и взорвать некоторые программы!
Итак, без лишних слов:
blow_up.cpp
Скомпилируйте, запустите и получите статус выхода:
Выход:
Обратите внимание, что такое поведение происходит только
-O0
в GCC 8.2: с-O3
, GCC предварительно вычисляет и оптимизирует все наши операции sNaN! Я не уверен, есть ли стандартный способ предотвратить это.Итак, из этого примера мы делаем вывод, что:
snan + 1.0
вызываетFE_INVALID
, ноqnan + 1.0
неLinux генерирует сигнал, только если он включен с помощью
feenableexept
.Это расширение glibc, я не нашел способа сделать это ни в одном стандарте.
Когда сигнал возникает, это связано с тем, что само оборудование ЦП вызывает исключение, которое ядро Linux обработало и сообщило приложению через сигнал.
Результатом является то , что отпечатки Баш
Floating point exception (core dumped)
, а статус выхода136
, который соответствует сигналу136 - 128 == 8
, который в соответствии с:есть
SIGFPE
.Обратите внимание, что
SIGFPE
это тот же сигнал, который мы получаем, если пытаемся разделить целое число на 0:хотя для целых чисел:
feenableexcept
Как справиться с SIGFPE?
Если вы просто создаете обработчик, который обычно возвращается, это приведет к бесконечному циклу, потому что после возврата обработчика деление происходит снова! Это можно проверить с помощью GDB.
Единственный способ - использовать
setjmp
иlongjmp
перейти в другое место, как показано на: C обработать сигнал SIGFPE и продолжить выполнениеКаковы реальные применения сетей SNAN?
Честно говоря, я до сих пор не понял суперполезного варианта использования sNaN. Его спросили на: Полезность сигнализации NaN?
sNaN кажутся особенно бесполезными, потому что мы можем обнаружить начальные недопустимые операции (
0.0f/0.0f
), которые генерируют qNaN с помощьюfeenableexcept
: похоже, чтоsnan
просто вызывает ошибки для большего количества операций, которыеqnan
не возникают , например, (qnan + 1.0f
).Например:
main.c
компилировать:
тогда:
дает:
а также:
дает:
См. Также: Как отследить NaN в C ++
Что такое сигнальные флаги и как ими манипулировать?
Все реализовано в аппаратном обеспечении ЦП.
Флаги находятся в каком-то регистре, как и бит, который говорит, следует ли поднять исключение / сигнал.
Эти регистры доступны из пользовательского пространства из большинства арок.
Эта часть кода glibc 2.29 на самом деле очень проста для понимания!
Например,
fetestexcept
для x86_86 это реализовано в sysdeps / x86_64 / fpu / ftestexcept.c :так что сразу видно, что инструкция по использованию
stmxcsr
Таким означает «Store MXCSR Register State».И
feenableexcept
реализовано в sysdeps / x86_64 / fpu / feenablxcpt.c :Что стандарт C говорит о qNaN и sNaN?
В проекте стандарта C11 N1570 прямо говорится, что стандарт не делает различий между ними в F.2.1 «Бесконечности, нули со знаком и NaN»:
Протестировано в Ubuntu 18.10, GCC 8.2. Апстримы GitHub:
источник