2010-01-06 6 views
5

Quelqu'un at-il à portée de main les extraits de code pour convertir un IEEE 754 double la immédiatement inférieure (resp. Supérieure) float, sans changer ou en supposant quoi que ce soit au sujet de l'arrondissement actuel du FPU mode?Conversion double pour flotter sans compter sur le mode d'arrondi FPU

Remarque: cette contrainte implique probablement de ne pas utiliser du tout la FPU. Je m'attends à ce que la façon la plus simple de le faire dans ces conditions soit de lire les bits du double en 64 bits et de travailler avec ça.

Vous pouvez supposer le boutisme de votre choix pour la simplicité, et que la double en question est disponible à travers le champ d de l'union ci-dessous:

union double_bits 
{ 
    long i; 
    double d; 
}; 

Je voudrais essayer de le faire moi-même mais je suis certain J'introduirais des bogues difficiles à remarquer pour les nombres dénormalisés ou négatifs.

+0

sur les systèmes glibc vous trouvez un fichier d'en-tête ieee754.h, qui définit les syndicats pour les types à virgule flottante et une structure de bitfield, de sorte que vous pouvez travailler avec la mantisse et l'exposant plus facile, désolé mais je ne peux pas vous donner un vrai code. – quinmars

Répondre

3

Je pense que les travaux suivants, mais je dirai d'abord mes hypothèses :

  • nombres à virgule flottante sont stockés au format IEEE-754 sur votre mise en œuvre,
  • Aucun débordement,
  • Vous avez nextafterf() disponible (cela est spécifié dans C99).

Aussi, très probablement, cette méthode n'est pas très efficace.

#include <stdio.h> 
#include <stdlib.h> 
#include <math.h> 

int main(int argc, char *argv[]) 
{ 
    /* Change to non-zero for superior, otherwise inferior */ 
    int superior = 0; 

    /* double value to convert */ 
    double d = 0.1; 

    float f; 
    double tmp = d; 

    if (argc > 1) 
     d = strtod(argv[1], NULL); 

    /* First, get an approximation of the double value */ 
    f = d; 

    /* Now, convert that back to double */ 
    tmp = f; 

    /* Print the numbers. %a is C99 */ 
    printf("Double: %.20f (%a)\n", d, d); 
    printf("Float: %.20f (%a)\n", f, f); 
    printf("tmp: %.20f (%a)\n", tmp, tmp); 

    if (superior) { 
     /* If we wanted superior, and got a smaller value, 
      get the next value */ 
     if (tmp < d) 
      f = nextafterf(f, INFINITY); 
    } else { 
     if (tmp > d) 
      f = nextafterf(f, -INFINITY); 
    } 
    printf("converted: %.20f (%a)\n", f, f); 

    return 0; 
} 

Sur ma machine, il imprime:

Double: 0.10000000000000000555 (0x1.999999999999ap-4) 
Float: 0.10000000149011611938 (0x1.99999ap-4) 
tmp: 0.10000000149011611938 (0x1.99999ap-4) 
converted: 0.09999999403953552246 (0x1.999998p-4) 

L'idée est que je suis en train de convertir la valeur double à une valeur float — cela pourrait être inférieure ou supérieure à la valeur double en fonction de la mode d'arrondi. Une fois reconverti en double, nous pouvons vérifier s'il est plus petit ou plus grand que la valeur originale. Ensuite, si la valeur du float n'est pas dans la bonne direction, nous regardons le prochain numéro float à partir du nombre converti dans la direction du numéro d'origine.

+0

Merci beaucoup pour ce code. Je devenais lentement convaincu que c'était la solution la moins sujette aux erreurs. Merci d'avoir signalé 'nextafterf' aussi, cela vaut beaucoup mieux que d'entrer/décrémenter les bits de' float' comme s'il s'agissait d'un 'int'. Pour atténuer le risque que 'f + 1' soit égal à' f', puis-je écrire 'nextafterf (f, INFINITY)' à la place? –

+0

Je viens de lire les pages de manuel, le brouillon standard en C, et je l'ai essayé, et j'ai l'impression que 'INFINITY' devrait fonctionner. –

+0

OK, j'ai édité mon post. Merci pour le commentaire. –

3

Pour faire ce travail avec plus de précision que de recombiner mantisse et chèque de bit exposant ceci:

http://www.mathworks.com/matlabcentral/fileexchange/23173

concernant

+0

Merci. La fonction 'double2halfp' est aussi compliquée que je le craignais, mais au moins, elle a déjà la moitié des constantes, donc c'est un bon point de départ. –

+0

Je voudrais utiliser le code donné comme référence et réécrire une approche plus simple, en utilisant & >> suivis par ou, puis vérifier les très petits et très grands nombres. Prenez le nombre de changements et la position des bits d'un coup d'oeil de http://babbage.cs.qc.edu/IEEE-754/Decimal.html – stacker

2

J'ai posté un code pour le faire ici: https://stackoverflow.com/q/19644895/364818 et copié ci-dessous pour votre commodité.

// d is IEEE double, but double is not natively supported. 
    static float ConvertDoubleToFloat(void* d) 
    { 
     unsigned long long x; 
     float f; // assumed to be IEEE float 
     unsigned long long sign ; 
     unsigned long long exponent; 
     unsigned long long mantissa; 

     memcpy(&x,d,8); 

     // IEEE binary64 format (unsupported) 
     sign  = (x >> 63) & 1; // 1 
     exponent = ((x >> 52) & 0x7FF); // 11 
     mantissa = (x >> 0) & 0x000FFFFFFFFFFFFFULL; // 52 
     exponent -= 1023; 

     // IEEE binary32 format (supported) 
     exponent += 127; // rebase 
     exponent &= 0xFF; 
     mantissa >>= (52-23); // left justify 

     x = mantissa | (exponent << 23) | (sign << 31); 
     memcpy(&f,&x,4); 

     return f; 
    } 
+0

Merci. La ligne 'exponent & = 0xFF;' signifie que quand il serait approprié de retourner '± FLT_MAX' ou' ± inf', un 'float' avec un exposant étrange est renvoyé à la place (et les résultats de dénormalisation sont désactivés aussi). –

Questions connexes