Полноценность передачи сигналов о NaN?

Я недавно читал вполне немного на IEEE 754 и x87 архитектуре. Я думал об использовании NaN как "отсутствующее значение" в некотором числовом коде вычисления, я продолжаю работать, и я надеялся, что использование сигнального NaN позволит мне ловить исключение в операции с плавающей запятой в случаях, где я не хочу возобновлять "отсутствующие значения". С другой стороны я использовал бы тихий NaN, чтобы позволить "отсутствующему значению" распространять посредством вычисления. Однако передача сигналов о NaNs не работает, поскольку я думал, что они будут на основе (очень ограниченный) документация, которая существует на них.

Вот сводка того, что я знаю (все это использование x87 и VC ++):

  • _EM_INVALID (IEEE "недопустимое" исключение) управляет поведением x87 при обнаружении с NaNs
  • Если _EM_INVALID маскируется (исключение отключено), никакое исключение не сгенерировано, и операции могут возвратить тихий NaN. Операционная передача сигналов вовлечения NaN не заставит исключение быть брошенным, но будет преобразован для подавления шумов NaN.
  • Если _EM_INVALID размаскирован (исключение включило), недопустимая операция (например, sqrt (-1)) заставляет недопустимое исключение быть брошенным.
  • x87 никогда не генерирует сигнальный NaN.
  • Если _EM_INVALID размаскирован, любое использование сигнального NaN (даже инициализирующий переменную с ним) заставляет недопустимое исключение быть брошенным.

Стандартная Библиотека позволяет получать доступ к значениям 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 для хранения его к адресу памяти).

Можно думать следующее, как я сделал:

  1. Маска _EM_INVALID.
  2. Инициализируйте переменную с передачей сигналов о NaN.
  3. Unmask_EM_INVALID.

Однако шаг 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.

50
задан 15 February 2010 в 04:52
поделиться

2 ответа

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

Я надеюсь, что вы сможете инициализировать статическое значение с сигнальным NaN, но даже это потребует некоторой специальной обработки со стороны компилятора, чтобы избежать преобразования его в тихий NaN. Возможно, вы могли бы использовать немного магии, чтобы не обрабатывать его как значение с плавающей запятой во время инициализации.

Если бы вы писали на ASM, это не было бы проблемой. но в C и особенно в C ++, я думаю, вам придется подорвать систему типов, чтобы инициализировать переменную с помощью NaN. Я предлагаю использовать memcpy .

10
ответ дан 7 November 2019 в 11:09
поделиться

Использование специальных значений (даже NULL) может сделать ваши данные намного более запутанными, а ваш код - более запутанным. Было бы невозможно отличить результат QNaN от «специального» значения QNaN.

Возможно, вам лучше поддерживать параллельную структуру данных для отслеживания достоверности или, возможно, хранить ваши FP-данные в другой (разреженной) структуре данных, чтобы хранить только действительные данные.

Это довольно общий совет; специальные значения очень полезны в определенных случаях (например, очень жесткая память или ограничения производительности), но по мере увеличения контекста они могут вызвать больше трудностей, чем они того стоят.

2
ответ дан 7 November 2019 в 11:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: