Question

Je l'ai lu récemment un peu sur IEEE 754 et l'architecture x87. Je pensais à l'aide NaN comme une « valeur manquante » dans un code de calcul numérique, je travaille, et j'espérais que l'utilisation de signalisation NaN me permettrait d'attraper un point flottant exception dans les cas où je ne veux pas procéder à des « valeurs manquantes. » A l'inverse, j'utiliser calme NaN pour permettre à la « valeur manquante » à se propager à travers un calcul. Cependant, NaN de signalisation ne fonctionnent pas comme je pensais qu'ils seraient basés sur le (très limité) la documentation qui existe sur eux.

Voici un résumé de ce que je sais (tout cela en utilisant x87 et VC ++):

  • _EM_INVALID (l'exception "invalide" IEEE) contrôle le comportement du x87 lors de la rencontre NaN
  • Si _EM_INVALID est masqué (l'exception est désactivée), aucune exception est générée et des opérations peut retourner NaN silencieux. Une opération impliquant NaN de signalisation sera pas provoque une exception à être jeté, mais sera convertie en NaN silencieux.
  • Si _EM_INVALID est démasqué (exception activée), une opération non valide (par exemple, sqrt (-1)) provoque une exception invalide à levée.
  • Le x87 jamais génère NaN de signalisation.
  • Si _EM_INVALID est démasqué, tout utilisation d'une NAN de signalisation (même l'initialisation d'une variable avec elle) provoque une exception invalide à levée.

La bibliothèque standard fournit un moyen d'accéder aux valeurs NaN:

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

et

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

Le problème est que je ne vois aucune utilité pour la NAN de signalisation. Si _EM_INVALID est masqué, il se comporte exactement le même que NaN silencieux. Puisqu'aucun NaN est comparable à tout autre NaN, il n'y a pas de différence logique.

Si _EM_INVALID est pas masqué (exception est activée), alors on ne peut pas initialiser même une variable avec une NAN de signalisation: double dVal = std::numeric_limits<double>::signaling_NaN(); parce que cela déclenche une exception (la signalisation de valeur NaN est chargée dans un registre x87 pour stocker à l'adresse de la mémoire).

Vous pouvez penser que ce qui suit comme je l'ai fait:

  1. Masque _EM_INVALID.
  2. Initialiser la variable avec NaN de signalisation.
  3. Unmask_EM_INVALID.

Cependant, l'étape 2 provoque la signalisation NaN être converti en un NaN calme, les utilisations suivantes afin de celui-ci sera pas exceptions faire jeter! Alors WTF?!

Y at-il l'utilité ou à une fin que ce soit NAN de signalisation? Je comprends l'une des intentions d'origine était d'initialiser la mémoire avec elle afin que l'utilisation d'une valeur à virgule flottante non initialisée pourrait être pris.

Quelqu'un peut-il me dire si je suis absent quelque chose ici?


EDIT:

Pour illustrer davantage ce que j'avais espéré faire, voici un exemple:

Pensez à effectuer des opérations mathématiques sur un vecteur de données (doubles). Pour certaines opérations, je veux permettre le vecteur de contenir une « valeur manquante » (faire semblant, ce qui correspond à une colonne de tableur, par exemple, dans lequel certaines des cellules n'ont pas une valeur, mais leur existence est importante). Pour certaines opérations, je ne pas veulent permettre le vecteur de contenir une « valeur manquante ». Peut-être que je veux suivre un cours d'action différent si une « valeur manquante » est présente dans l'ensemble - peut-être effectuer une autre opération (donc ce n'est pas un état non valide pour être)

.

Ce code original ressemblerait à quelque chose comme ceci:

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;
}

Notez que la vérification de la « valeur manquante » doit être effectuée chaque itération de boucle . Même si je comprends dans la plupart des cas, la fonction sqrt (ou toute autre opération mathématique) éclipser probablement cette vérification, il y a des cas où l'opération est minime (peut-être juste un ajout) et le chèque est coûteux. Sans parler du fait que la « valeur manquante » prend une valeur d'entrée légale sur le jeu et pourrait causer des bugs si un calcul légitimearrive à cette valeur (peu probable que cela puisse être). De plus techniquement correct, les données d'entrée utilisateur doivent être vérifiées par rapport à cette valeur et un plan d'action approprié doivent être prises. Je trouve cette solution inélégante et moins-que-optimal en terme de performance. Ce code est la performance critique, et nous ne certainement pas le luxe de structures de données parallèles ou des objets d'éléments de données de quelque sorte.

La version NaN ressemblerait à ceci:

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;
    }
}

Maintenant, la vérification explicite est éliminé et la performance devrait être améliorée. Je pense que ce serait tout le travail si je pouvais initialiser le vecteur sans toucher les registres FPU ...

Par ailleurs, j'imagine des auto-respect des contrôles de mise en œuvre de sqrt pour NaN et renvoie NaN immédiatement.

Était-ce utile?

La solution

Si je comprends bien, le but de la signalisation NaN est d'initialiser des structures de données, mais, bien sûr runtime initialisation en C court le risque d'avoir le NaN chargé dans un registre à flotteur dans le cadre de l'initialisation , déclenchant ainsi le signal parce que le compilateur ne sait pas que cette valeur flottante doit être copié en utilisant un registre de nombre entier.

J'espère que vous pourriez pu initialiser une valeur static avec un NaN de signalisation, mais même cela nécessiterait une manipulation spéciale par le compilateur pour éviter l'avoir converti en un NaN calme. Vous pourriez peut-être utiliser un peu de coulée magique pour éviter l'avoir traité comme une valeur flottante lors de l'initialisation.

Si vous écriviez dans ASM, ce ne serait pas un problème. mais en C et en particulier en C ++, je pense que vous aurez à subvertir le système de type afin d'initialiser une variable avec NaN. Je suggère d'utiliser memcpy.

Autres conseils

Utilisation de valeurs spéciales (même NULL) peut rendre vos données beaucoup et boueuses votre code beaucoup malpropres. Il serait impossible de faire la distinction entre un résultat QNAN et une QNAN valeur « spéciale ».

Vous pourriez être mieux maintenir une structure de données en parallèle pour suivre la validité, ou peut-être avoir vos données de PF dans une structure de données différentes (rares) pour ne conserver que des données valides.

Ce conseil est assez général; Les valeurs spéciales sont très utiles dans certains cas (par exemple, des contraintes de mémoire ou de performance très serré), mais le contexte devient plus grand, ils peuvent causer plus de mal que ce qu'ils valent.

Vous ne pourriez pas avoir juste un uint64_t const où les bits ont été mis à ceux d'une signalisation nan? aussi longtemps que vous le traiter comme un type entier, la signalisation nan n'est pas différent des autres entiers. Vous pouvez écrire où vous voulez par pointer-casting:

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

Pour plus d'infos sur les bits pour définir: https://www.doc.ic.ac.uk/ ~ eedwards / Compsys / flotteur / nan.html

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top