¿Cuál es el propósito del boxeo NaN?

44

Leyendo el siglo XXI C Llegué al capítulo 6 en la sección "Marcado de valores numéricos excepcionales con NaN" , donde explica el uso de los bits en la mantisa para almacenar algunos patrones de bits arbitrarios, para usarlos como marcadores o punteros (el libro menciona que WebKit usa esta técnica).

No estoy realmente seguro de haber entendido la utilidad de esta técnica, que veo como un truco (se basa en que el hardware no se preocupa por el valor de la mantisa en un NaN) sino que proviene de un fondo de Java al que no estoy acostumbrado la aspereza de C.

Aquí está el fragmento de código que establece y lee un marcador en un NaN

#include <stdio.h>
#include <math.h> //isnan

double ref;

double set_na(){
    if (!ref) {
        ref=0/0.;
        char *cr = (char *)(&ref);
        cr[2]='a';
    }
    return ref;
}

int is_na(double in){
    if (!ref) return 0;  //set_na was never called==>no NAs yet.

    char *cc = (char *)(&in);
    char *cr = (char *)(&ref);
    for (int i=0; i< sizeof(double); i++)
        if (cc[i] != cr[i]) return 0;
    return 1;
}

int main(){
    double x = set_na();
    double y = x;
    printf("Is x=set_na() NA? %i\n", is_na(x));
    printf("Is x=set_na() NAN? %i\n", isnan(x));
    printf("Is y=x NA? %i\n", is_na(y));
    printf("Is 0/0 NA? %i\n", is_na(0/0.));
    printf("Is 8 NA? %i\n", is_na(8));
}

imprime:

Is x=set_na() NA? 1
Is x=set_na() NAN? 1
Is y=x NA? 1
Is 0/0 NA? 0
Is 8 NA? 0

y en JSValue.h webkit explica la codificación, pero no por qué se usa.

¿Cuál es el propósito de esta técnica? ¿Son los beneficios del espacio / rendimiento lo suficientemente altos como para equilibrar su naturaleza agresiva?

andijcr
fuente
¿Puedes dar un ejemplo simple?
B 31овић
Para que quede claro el PO está pidiendo donde NaNs de señalización se pueden utilizar
trinquete monstruo
1
@ratchetfreak, ¿qué te hace pensar eso?
Winston Ewert
@ratchetfreak: la pregunta no es sobre señalar NaN, como explica el kit web JSValue.h, ¡Pero gracias por permitirme descubrir algo nuevo!
andijcr
1
@Hudson isnan () si se usa en el segundo printf en el principal. El propósito de is_an () es probar si el patrón de bits del doble en la entrada es igual al guardado dentro de la variable global ref.
andijcr

Respuestas:

63

Cuando implementa un lenguaje de tipo dinámico, debe tener un solo tipo que pueda contener cualquiera de sus objetos. Hay tres enfoques diferentes que conozco para esto:

En primer lugar, puede pasar punteros. Esto es lo que hace la implementación de CPython. Todo objeto es un PyObjectpuntero. Estos punteros se pasan y las operaciones se realizan mirando los detalles en la estructura PyObject para descubrir el tipo.

La desventaja es que los valores pequeños como los números se almacenan como valores encuadrados, por lo que sus 5 pequeños se almacenan como un bloque de memoria en alguna parte. Entonces esto nos lleva al enfoque sindical, que es utilizado por Lua. En lugar de a PyObject*, cada valor es una estructura de un campo para especificar el tipo, y luego una unión de todos los diferentes tipos admitidos. De esta forma, evitamos asignar memoria a valores pequeños, en lugar de almacenarlos directamente en la unión.

El NaNenfoque almacena todo como dobles y reutiliza la parte no utilizada NaNpara el almacenamiento adicional. La ventaja sobre el método de unión es que guardamos el campo de tipo. Si es un doble válido, es un doble, de lo contrario, la mantisa es un puntero al objeto real.

Recuerde, este es cada objeto javascript. Cada variable, cada valor en un objeto, cada expresión. Si podemos reducir todos esos de 96 bits a 64 bits, eso es bastante impresionante.

¿Vale la pena el hack? Recuerde que hay mucha demanda de Javascript eficiente. Javascript es el cuello de botella en muchas aplicaciones web, por lo que hacerlo más rápido es una prioridad más alta. Es razonable introducir un cierto grado de piratería por motivos de rendimiento. Para la mayoría de los casos, sería una mala idea, ya que introduce un grado de complejidad con poca ganancia. Pero en este caso específico, vale la pena mejorar la memoria y la velocidad.

Winston Ewert
fuente
2
En realidad, CPython almacena en caché números pequeños. Ver hg.python.org/cpython/file/e6cc582cafce/Objects/longobject.c
Phillip Cloud el
1
@cpcloud, cierto, pero ese detalle no parecía pertinente.
Winston Ewert
1
@ WinstonEwert Tienes razón. Pensé lo mismo después de leer lo que había escrito.
Phillip Cloud
2
Usar bits de un tipo primitivo para evitar "encajonar" todos los valores es una técnica tradicional. Smalltalk lo usó en la década de 1970, robando un bit de enteros de 16 bits para señalar un puntero de objeto o 15 bits SmallInteger.
Jonathan Eunice
2
@ JonathanEunice, ¿en serio? Eso simplemente me sorprende porque realmente no hay un largo rango en 16 bits que estaría dispuesto a renunciar un poco.
Winston Ewert
7

Usar NaN para "valores excepcionales" es una técnica bien conocida y a veces útil para evitar la necesidad de una variable booleana adicional this_value_is_invalid. Utilizado con prudencia, puede ayudarlo a hacer que su código sea más conciso, más limpio, más simple, mejor legible sin ninguna compensación de rendimiento.

Esta técnica tiene algunas dificultades, por supuesto (ver aquí http://ppkwok.blogspot.co.uk/2012/11/java-cafe-1-never-write-nan-nan_24.html ), pero en lenguajes como Java ( o C # muy similar) hay funciones de biblioteca estándar como Float.isNaNhacer que tratar con NaNs sea simple. Por supuesto, en Java se puede utilizar como alternativa el Floaty Doubleclase y en C # del anulable tipos de valor float?y double?, dándole la posibilidad de utilizar nullen lugar de NaN para los números de punto flotante no válidas, pero esas técnicas pueden tener una influencia negativa significativa en el rendimiento y la memoria uso de su programa.

En C, el uso de NaN no es 100% portátil, eso es cierto, pero puede usarlo en cualquier lugar donde esté disponible el estándar de punto flotante IEEE 754. AFAIK esto es casi todo el hardware convencional actual (o al menos el entorno de ejecución de la mayoría de los compiladores lo admite). Por ejemplo, esta publicación SO contiene información para obtener más detalles sobre el uso de NaN en C.

Doc Brown
fuente
la auto-boxing en Java es complicado y debe ser evitado, simplemente usarlo para ser capaz de proporcionar un valor nulo es ridícula y propenso a errores
trinquete monstruo
Edité la pregunta para vincular a donde webkit usa NaN-boxing. Parece que webkit tiene un uso más amplio de NaN, que no sea para señalar 'NaN'
andijcr
2
@ratchetfreak: eso apoya mi punto, por supuesto
Doc Brown