Я недавно читал вполне немного на IEEE 754 и x87 архитектуре. Я думал об использовании NaN как "отсутствующее значение" в некотором числовом коде вычисления, я продолжаю работать, и я надеялся, что использование сигнального NaN позволит мне ловить исключение в операции с плавающей запятой в случаях, где я не хочу возобновлять "отсутствующие значения". С другой стороны я использовал бы тихий NaN, чтобы позволить "отсутствующему значению" распространять посредством вычисления. Однако передача сигналов о NaNs не работает, поскольку я думал, что они будут на основе (очень ограниченный) документация, которая существует на них.
Вот сводка того, что я знаю (все это использование x87 и VC ++):
Стандартная Библиотека позволяет получать доступ к значениям NaN:
std::numeric_limits<double>::signaling_NaN();
и
std::numeric_limits<double>::quiet_NaN();
Проблема состоит в том, что я вижу быть бесполезное вообще для сигнального NaN. Если _EM_INVALID маскируется, он ведет себя точно то же как тихий NaN. Так как никакой NaN не сопоставим ни с каким другим NaN, нет никакого логического различия.
Если _EM_INVALID не маскируется (исключение включено), то нельзя даже инициализировать переменную с сигнальным NaN: double dVal = std::numeric_limits<double>::signaling_NaN();
потому что это выдает исключение (сигнальное значение NaN загружается в регистр x87 для хранения его к адресу памяти).
Можно думать следующее, как я сделал:
Однако шаг 2 заставляет сигнальный NaN быть преобразованным в тихий NaN, таким образом, последующее использование его не заставит исключения быть брошенными! Так WTF?!
Там кто-либо - утилита или цель вообще к сигнальному NaN? Я понимаю, что одно из исходных намерений состояло в том, чтобы инициализировать память с ним так, чтобы использование unitialized значения с плавающей точкой могло быть поймано.
Кто-то может сказать мне, если я пропускаю что-то здесь?
Править:
Для дальнейшего иллюстрирования, что я надеялся сделать вот, пример:
Полагайте, что работающие математические операции на векторе данных (удваиваются). Для некоторых операций я хочу позволить вектору содержать "отсутствующее значение" (притворитесь, что это соответствует столбцу электронной таблицы, например, в котором некоторые ячейки не имеют значения, но их существование является значительным). Для некоторых операций я не хочу позволять вектору содержать "отсутствующее значение". Возможно, я хочу взять другой план действий, если "отсутствующее значение" присутствует в наборе - возможно, выполнение другой операции (таким образом, это не недопустимое состояние, чтобы быть в).
Этот исходный код выглядел бы примерно так:
const double MISSING_VALUE = 1.3579246e123;
using std::vector;
vector<double> missingAllowed(1000000, MISSING_VALUE);
vector<double> missingNotAllowed(1000000, MISSING_VALUE);
// ... populate missingAllowed and missingNotAllowed with (user) data...
for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
if (*it != MISSING_VALUE) *it = sqrt(*it); // sqrt() could be any operation
}
for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
if (*it != MISSING_VALUE) *it = sqrt(*it);
else *it = 0;
}
Обратите внимание, что проверка на "отсутствующее значение" должна быть выполнена каждое повторение цикла. В то время как я понимаю в большинстве случаев, sqrt
функция (или любая другая математическая операция), вероятно, омрачит эту проверку, существуют случаи, где операция минимальна (возможно, просто дополнение), и проверка является дорогостоящей. Не говоря уже о том, что "отсутствующее значение" вынимает легальное входное значение из игры и могло вызвать ошибки, если вычисление законно прибывает в то значение (вряд ли, хотя это может быть). Также, чтобы быть технически корректными, данные ввода данных пользователем должны быть проверены по тому значению, и должен быть взят соответствующий план действий. Я нахожу это решение неэлегантным и меньше оптимальным мудрый производительностью. Это - критический по отношению к производительности код, и у нас определенно нет роскоши параллельных структур данных или каких-то объектов элемента данных.
Версия NaN была бы похожа на это:
using std::vector;
vector<double> missingAllowed(1000000, std::numeric_limits<double>::quiet_NaN());
vector<double> missingNotAllowed(1000000, std::numeric_limits<double>::signaling_NaN());
// ... populate missingAllowed and missingNotAllowed with (user) data...
for (vector<double>::iterator it = missingAllowed.begin(); it != missingAllowed.end(); ++it) {
*it = sqrt(*it); // if *it == QNaN then sqrt(*it) == QNaN
}
for (vector<double>::iterator it = missingNotAllowed.begin(); it != missingNotAllowed.end(); ++it) {
try {
*it = sqrt(*it);
} catch (FPInvalidException&) { // assuming _seh_translator set up
*it = 0;
}
}
Теперь явная проверка устраняется, и производительность должна быть улучшена. Я думаю, что это все работало бы, если я мог бы инициализировать вектор, не касаясь регистров FPU...
Кроме того, я вообразил бы любого обладающим чувством собственного достоинства sqrt
реализация сразу проверяет на NaN и возвраты NaN.
Насколько я понимаю, цель сигнализации NaN - инициализировать структуры данных, но, конечно, инициализация времени выполнения в C выполняется риск того, что NaN будет загружено в регистр с плавающей запятой как часть инициализации, тем самым вызывая сигнал, потому что компилятор не знает, что это значение с плавающей запятой необходимо скопировать с использованием целочисленного регистра.
Я надеюсь, что вы сможете инициализировать статическое
значение с сигнальным NaN, но даже это потребует некоторой специальной обработки со стороны компилятора, чтобы избежать преобразования его в тихий NaN. Возможно, вы могли бы использовать немного магии, чтобы не обрабатывать его как значение с плавающей запятой во время инициализации.
Если бы вы писали на ASM, это не было бы проблемой. но в C и особенно в C ++, я думаю, вам придется подорвать систему типов, чтобы инициализировать переменную с помощью NaN. Я предлагаю использовать memcpy
.
Использование специальных значений (даже NULL) может сделать ваши данные намного более запутанными, а ваш код - более запутанным. Было бы невозможно отличить результат QNaN от «специального» значения QNaN.
Возможно, вам лучше поддерживать параллельную структуру данных для отслеживания достоверности или, возможно, хранить ваши FP-данные в другой (разреженной) структуре данных, чтобы хранить только действительные данные.
Это довольно общий совет; специальные значения очень полезны в определенных случаях (например, очень жесткая память или ограничения производительности), но по мере увеличения контекста они могут вызвать больше трудностей, чем они того стоят.