Recientemente he leído bastante sobre IEEE 754 y la arquitectura x87. Estaba pensando en usar NaN como un "valor perdido" en algún código de cálculo numérico en el que estoy trabajando, y esperaba que el uso de de señalización NaN me permitiera detectar una excepción de punto flotante en los casos en que no lo hago quiero proceder con "valores perdidos". Por el contrario, usaría NaN para permitir que el "valor perdido" se propague a través de un cálculo. Sin embargo, los NaN de señalización no funcionan como pensé que basarían en la documentación (muy limitada) que existe sobre ellos.Utilidad de la señalización NaN?
Aquí se presenta un resumen de lo que sé (todo esto utilizando x87 y VC++):
- _EM_INVALID (IEEE excepción "no válido") controla el comportamiento de la x87 cuando se enfrentan a NaNs
- Si _EM_INVALID está enmascarado (la excepción está deshabilitada), no se genera ninguna excepción y las operaciones pueden devolver NaN silencioso. Una operación que implica la señalización de NaN será no causará una excepción, pero se convertirá en NaN silencioso.
- Si _EM_INVALID se desenmascara (excepción habilitada), una operación no válida (por ejemplo, sqrt (-1)) provoca una excepción no válida.
- El x87 nunca genera NaN de señalización.
- Si _EM_INVALID se desenmascara, cualquier uso de un NaN de señalización (incluso inicializando una variable con él) provoca una excepción no válida que se lanzará.
la biblioteca estándar proporciona una manera de acceder a los valores NaN:
std::numeric_limits<double>::signaling_NaN();
y
std::numeric_limits<double>::quiet_NaN();
El problema es que no veo utilidad alguna para la señalización NaN. Si _EM_INVALID está enmascarado se comporta exactamente igual que NaN silencioso. Como no NaN es comparable a cualquier otro NaN, no hay diferencia lógica.
Si _EM_INVALID es no enmascarado (excepción está activado), entonces ni siquiera se puede inicializar una variable con una señalización NaN: double dVal = std::numeric_limits<double>::signaling_NaN();
porque esto produce una excepción (el valor NaN de señalización se carga en un x87 registro para almacenarlo a la dirección de memoria).
usted puede pensar lo siguiente como lo hice:
- _EM_INVALID máscara.
- Inicialice la variable con NaN de señalización.
- Unmask_EM_INVALID.
Sin embargo, el paso 2 hace que la señalización NaN a convertir en una tranquila NaN, se no excepciones causa que se arrojen usos posteriores por lo que de ella! Entonces WTF ?!
¿Hay alguna utilidad o propósito en absoluto para un NaN de señalización? Entiendo que una de las intenciones originales fue inicializar la memoria con ella para poder capturar el uso de un valor de coma flotante unificado.
¿Puede alguien decirme si me falta algo aquí?
EDIT:
Para ilustrar mejor lo que había esperado hacer, aquí es un ejemplo:
Considere realizar operaciones matemáticas en un vector de datos (dobles). Para algunas operaciones, quiero permitir que el vector contenga un "valor perdido" (pretender que corresponde a una columna de hoja de cálculo, por ejemplo, en la que algunas de las celdas no tienen un valor, pero su existencia es significativa). Para algunas operaciones, hago no quiero permitir que el vector contenga un "valor faltante". Quizás quiera tomar un curso de acción diferente si hay un "valor perdido" en el conjunto, tal vez realizando una operación diferente (por lo tanto, este no es un estado inválido).
Este código original sería algo como esto:
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;
}
Tenga en cuenta que el cheque por el "valor faltante" debe realizarse cada repetición del bucle. Aunque entiendo en la mayoría de los casos, la función sqrt
(o cualquier otra operación matemática) probablemente eclipsará esta verificación, hay casos donde la operación es mínima (quizás solo una adición) y el cheque es costoso. Sin mencionar el hecho de que el "valor perdido" tiene un valor de entrada legal fuera de juego y podría causar errores si un cálculo llega legítimamente a ese valor (por improbable que sea). Además, para ser técnicamente correcto, los datos de entrada del usuario se deben comparar con ese valor y se debe tomar un curso de acción apropiado. Encuentro esta solución poco elegante y menos que óptima en cuanto a rendimiento. Este es un código de rendimiento crítico, y definitivamente no tenemos el lujo de estructuras de datos paralelas u objetos de elementos de datos de algún tipo.
versión Nan se vería así:
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;
}
}
Ahora la comprobación explícita se elimina y el rendimiento debe ser mejorado. Creo que todo esto funcionaría si pudiera inicializar el vector sin tocar los registros de la FPU ...
Además, me imagino que cualquier implementación que se precie de sqrt
verifica NaN y devuelve NaN inmediatamente.
Buena pregunta. Desafortunadamente, el único uso que he visto para señalar NaNs es generar una llamada a mi teléfono celular a las 9:30 PM del sábado. –