Utilidad de la señalización NaN?


Recientemente he leído bastante sobre IEEE 754 y la arquitectura x87. Estaba pensando en usar NaN como un" valor faltante "en algún código de cálculo numérico en el que estoy trabajando, y esperaba que usar señalización NaN me permitiera capturar una excepción de coma flotante en los casos en los que no quiero proceder con "valores faltantes"."Por el contrario, usaría quiet NaN para permitir que el "valor faltante" se propague a través de un cálculo. Sin embargo, las NAN de señalización no funcionan como Pensé que lo harían sobre la base de la (muy limitada) documentación que existe en ellos.

Aquí hay un resumen de lo que sé (todo esto usando x87 y VC++):

  • _EM_INVALID (la excepción "inválida" de IEEE) controla el comportamiento del x87 cuando se encuentra con NaNs
  • Si _EM_INVALID está enmascarado (la excepción está desactivada), no se genera ninguna excepción y las operaciones pueden devolver NAN silencioso. Una operación que implique la señalización NaN no causará que una excepción sea lanzado, pero se convertirá en NaN tranquila.
  • Si _EM_INVALID está desenmascarado (excepción habilitada), una operación no válida (por ejemplo, sqrt(-1)) causa que se lance una excepción no válida.
  • El x87 nunca genera NAN de señalización.
  • Si se desenmascara _EM_INVALID, cualquier uso de una NaN de señalización (incluso inicializando una variable con ella) causa que se lance una excepción no válida.

La Biblioteca Estándar proporciona una forma de acceder a la NaN valores:

std::numeric_limits<double>::signaling_NaN();

Y

std::numeric_limits<double>::quiet_NaN();

El problema es que no veo ningún uso para la señalización NaN. Si _EM_INVALID está enmascarado, se comporta exactamente igual que el NaN silencioso. Dado que ningún NaN es comparable a cualquier otro NaN, no hay diferencia lógica.

Si _EM_INVALID está no enmascarado( la excepción está habilitada), entonces no se puede inicializar una variable con una NaN de señalización: double dVal = std::numeric_limits<double>::signaling_NaN(); porque esto arroja una excepción (el valor de NAN de señalización se carga en un registro x87 para almacenarlo en la dirección de memoria).

Usted puede pensar lo siguiente como lo hice yo:{[12]]}

  1. Máscara _EM_INVALID.
  2. Inicializa la variable con NAN de señalización.
  3. Unmask_EM_INVALID.

Sin embargo, el paso 2 hace que la NaN de señalización se convierta en una NAN silenciosa, por lo que los usos posteriores de la misma no causarán que se produzcan excepciones. ¿Así que WTF?!

¿Hay alguna utilidad o propósito para una NaN de señalización? Entiendo uno de los originales intents era inicializar la memoria con ella para que el uso de un valor de punto flotante unitializado pudiera ser capturado.

¿Puede alguien decirme si me estoy perdiendo algo aquí?


EDITAR:

Para ilustrar aún más lo que esperaba hacer, aquí hay un ejemplo:{[12]]}

Considere realizar operaciones matemáticas en un vector de datos (dobles). Para algunas operaciones, quiero permitir que el vector contenga un " valor faltante "(simule que esto corresponde a una hoja de cálculo columna, por ejemplo, en la que algunas de las celdas no tienen un valor, pero su existencia es significativa). Para algunas operaciones, quiero no permitir que el vector contenga un "valor faltante"."Tal vez quiero tomar un curso de acción diferente si un "valor faltante" está presente en el conjunto perhaps tal vez realizar una operación diferente (por lo tanto, este no es un estado inválido para estar).

Este código original se verí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 la comprobación de la el "valor faltante" se debe realizar cada iteración de bucle. Aunque entiendo que en la mayoría de los casos, la función sqrt (o cualquier otra operación matemática) probablemente eclipsará esta comprobación, hay casos en los que la operación es mínima (tal vez solo una adición) y la comprobación es costosa. Sin mencionar el hecho de que el "valor faltante" deja fuera de juego un valor de entrada legal y podría causar errores si un cálculo llega legítimamente a ese valor (aunque sea improbable). También para ser técnicamente correctos, los datos de entrada del usuario deben cotejarse con ese valor y debe adoptarse un curso de acción adecuado. Encuentro esta solución poco elegante y menos que óptima en cuanto a rendimiento. Este es un código crítico para el rendimiento, y definitivamente no tenemos el lujo de estructuras de datos paralelas u objetos de elementos de datos de algún tipo.

La versión de 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 se elimina la comprobación explícita y se debe mejorar el rendimiento. Creo que todo esto funcionaría si pudiera inicializar el vector sin tocar los registros FPU...

Además, me imagino que cualquier sqrt implementación que se respete comprueba para NaN y devuelve Nan inmediatamente.

Author: John Knoeller, 2010-02-11

3 answers

Según lo entiendo, el propósito de la señalización NaN es inicializar estructuras de datos, pero, por supuesto tiempo de ejecución la inicialización en C corre el riesgo de tener la NaN cargada en un registro flotante como parte de la inicialización, activando así la señal porque el compilador no es consciente de que este valor flotante necesita ser copiado usando un registro entero.

Espero que pueda inicializar un valor static con una NaN de señalización, pero incluso eso requeriría algo especial manejo por el compilador para evitar que se convierta en una NaN silenciosa. Quizás podría usar un poco de magia de fundición para evitar que se trate como un valor flotante durante la inicialización.

Si estuviera escribiendo en ASM, esto no sería un problema. pero en C y especialmente en C++, creo que tendrás que subvertir el sistema de tipos para inicializar una variable con NaN. Sugiero usar memcpy.

 9
Author: John Knoeller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-11 21:51:16

El uso de valores especiales (incluso NULL) puede hacer que sus datos sean mucho más muddier y su código mucho más desordenado. Sería imposible distinguir entre un resultado de QNaN y un valor "especial" de QNaN.

Podría ser mejor mantener una estructura de datos paralela para rastrear la validez, o tal vez tener sus datos FP en una estructura de datos diferente (dispersa) para mantener solo los datos válidos.

Este es un consejo bastante general; los valores especiales son muy útiles en ciertos casos (por ejemplo, limitaciones de rendimiento), pero a medida que el contexto se hace más grande, pueden causar más dificultad de la que valen.

 2
Author: Matt Curtis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-02-13 06:05:37

¿No podría simplemente tener un const uint64_t donde los bits se han establecido en los de una nan de señalización? mientras lo trates como un tipo entero, la nan de señalización no es diferente de otros enteros. Puedes escribirlo donde quieras a través de pointer-casting:

Const uint64_t sNan = 0xfff0000000000000;
Double[] myData;
...
Uint64* copier = (uint64_t*) &myData[index];
*copier=sNan | myErrorFlags;

Para obtener información sobre los bits a establecer: https://www.doc.ic.ac.uk / ~eedwards/compsys/float/nan.html

 2
Author: Jan Heldal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-17 18:55:03