Quel est le but de la boxe NaN?

44

À la lecture de 21st Century C, je suis arrivé au chapitre 6 à la section "Marquage de valeurs numériques exceptionnelles avec NaN" , où il est expliqué comment utiliser les bits de la mantisse pour stocker des motifs de bits arbitraires, pour les utiliser comme marqueurs ou pointeurs (le livre mentionne que WebKit utilise cette technique).

Je ne suis pas vraiment sûr d'avoir compris l'utilité de cette technique, que je vois comme un bidouillage (le matériel ne tient pas compte de la valeur de la mantisse dans un NaN) mais venant d'un arrière-plan Java auquel je ne suis pas habitué la rugosité de C.

Voici l'extrait de code qui définit et lit un marqueur dans 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));
}

il 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

et à JSValue.h webkit explique le codage, mais pas pourquoi il est utilisé.

Quel est le but de cette technique? Les avantages de l’espace / de la performance sont-ils suffisamment élevés pour contrebalancer sa nature rudimentaire?

andijcr
la source
pouvez-vous donner un exemple simple?
Soirée
être clair l'OP demande où signalisation NaN peuvent être utilisés
freak cliquet
1
@ ratchetfreak, qu'est-ce qui te fait penser ça?
Winston Ewert le
@ratchetfreak: il ne s'agit pas de signaler NaN, comme l'explique le kit Web JSValue.h, mais merci de m'avoir laissé découvrir quelque chose de nouveau!
Andijcr
1
@Hudson isnan () si est utilisé dans le deuxième printf dans la partie principale. Is_an () a pour but de tester si le motif binaire de l'entrée double est égal à celui enregistré dans la variable globale ref.
Andijcr

Réponses:

63

Lorsque vous implémentez un langage typé dynamiquement, vous devez avoir un seul type pouvant contenir n'importe lequel de vos objets. Je connais trois approches différentes pour cela:

Tout d'abord, vous pouvez faire passer des pointeurs. C'est ce que fait l'implémentation CPython. Chaque objet est un PyObjectpointeur. Ces pointeurs sont distribués et les opérations sont effectuées en examinant les détails dans la structure PyObject pour déterminer le type.

L'inconvénient est que les petites valeurs telles que les nombres sont stockées sous forme de valeurs encadrées. Ainsi, votre petit 5 est stocké sous forme de bloc de mémoire quelque part. Cela nous conduit donc à l’approche d’union, utilisée par Lua. Au lieu de a PyObject*, chaque valeur est une structure dans laquelle un champ spécifie le type, puis une union de tous les différents types pris en charge. De cette façon, nous évitons d'allouer de la mémoire pour de petites valeurs, mais de les stocker directement dans l'union.

L’ NaNapproche stocke tout en double et réutilise la partie inutilisée NaNpour un espace de stockage supplémentaire. L'avantage par rapport à la méthode d'union est que nous sauvegardons le champ de type. Si c'est un double valide, c'est un double, sinon la mantisse est un pointeur sur l'objet réel.

N'oubliez pas qu'il s'agit de tous les objets javascript. Chaque variable, chaque valeur dans un objet, chaque expression. Si nous pouvons réduire toutes ces fréquences de 96 bits à 64 bits, c'est assez impressionnant.

Vaut-il le bidouillage? Rappelez-vous qu'il existe une forte demande pour un Javascript efficace. Javascript est le goulot d'étranglement dans de nombreuses applications Web, il est donc prioritaire de l'accélérer. Il est raisonnable d'introduire un certain degré de hackiness pour des raisons de performances. Dans la plupart des cas, ce serait une mauvaise idée, car il introduit un degré de complexité pour un gain minime. Mais dans ce cas précis, cela vaut la peine d’améliorer la mémoire et la vitesse.

Winston Ewert
la source
2
En réalité, CPython met en cache de petits nombres. Voir hg.python.org/cpython/file/e6cc582cafce/Objects/longobject.c
Phillip Cloud
1
@cpcloud, c'est vrai, mais ce détail ne semblait pas pertinent.
Winston Ewert
1
@ WinstonEwert Vous avez raison. Je pensais la même chose après avoir lu ce que j'avais écrit.
Phillip Cloud
3
Utiliser des bits de type primitif pour éviter de "mettre en boîte" toutes les valeurs est une technique reconnue. Smalltalk l'a utilisé dans les années 1970, dérobant un bit d'entiers de 16 bits pour signaler un pointeur d'objet ou 15 bits SmallInteger.
Jonathan Eunice
2
@ JonathanEunice, vraiment? Cela me surprend car il n’ya vraiment pas une longue plage en 16 bits que je serais prêt à abandonner un peu.
Winston Ewert
7

Utiliser NaN pour des "valeurs exceptionnelles" est une technique bien connue et parfois utile pour éviter le recours à une variable booléenne supplémentaire this_value_is_invalid. Utilisé à bon escient, il peut aider à rendre son code plus concis, plus propre, plus simple et plus lisible sans compromis en termes de performances.

Cette technique a bien sûr quelques pièges (voir ici http://ppkwok.blogspot.co.uk/2012/11/java-cafe-1-never-write-nan-nan_24.html ), mais dans des langages comme Java ( ou très similaire en C #), il existe des fonctions de bibliothèque standard Float.isNaNpermettant de traiter facilement les NaN. Bien sûr, en Java , vous pouvez utiliser alternativement la Floatet Doubleclasse et en C # types de valeurs annulable float?et double?, en vous donnant la possibilité d'utiliser au nulllieu de NaN pour les nombres à virgule flottante non valides, mais ces techniques peuvent avoir une influence négative importante sur la performance et de la mémoire utilisation de votre programme.

En C, l’utilisation de NaN n’est pas portable à 100%, c’est vrai, mais vous pouvez l’utiliser partout où la norme à virgule flottante IEEE 754 est disponible. Autant que je sache, il s’agit à peu près de tous les matériels traditionnels (ou du moins l’environnement d’exécution de la plupart des compilateurs le supporte). Par exemple, cet article sur le SO contient des informations pour en savoir plus sur l'utilisation de NaN en C.

Doc Brown
la source
l'auto-boxing en java est désordonné et devrait être évité, le simple fait de l'utiliser pour pouvoir fournir une valeur nulle est ridicule et sujet aux bugs
phénomène de cliquet
J'ai édité la question pour créer un lien vers où webkit utilise NaN-boxing. Il semble que webkit utilise plus largement NaN, autre que pour signaler 'NaN'
andijcr
2
@ ratchetfreak: cela confirme mon propos, bien sûr
Doc Brown le