2009-03-13 7 views
3

Ceci est probablement une question pour un expert x86 FPU:arrondi à virgule flottante lorsque tronquer

Je suis en train d'écrire une fonction qui génère une valeur de virgule flottante aléatoire dans l'intervalle [min, max]. Le problème est que mon algorithme de générateur (le Twister Mersenne à virgule flottante, si vous êtes curieux) ne renvoie que des valeurs dans la plage [1,2] - ie, je veux une limite supérieure inclusive, mais ma valeur générée "source" est à partir d'une limite supérieure exclusive. La capture ici est que le générateur sous-jacent renvoie un double de 8 octets, mais je veux seulement un flottant de 4 octets, et j'utilise le mode d'arrondi FPU par défaut de Nearest. Ce que je veux savoir est si la troncature elle-même dans ce cas résultera dans ma valeur de retour en incluant le maximum quand la valeur 80-bit interne de FPU est suffisamment proche, ou si je devrais augmenter le significand de ma valeur maximum avant de le multiplier par l'intermédiaire aléatoire dans [1,2), ou si je devrais changer de mode FPU. Ou bien d'autres idées, bien sûr.

Voici le code que je suis actuellement en utilisant, et je vérifiaient que 1.0f décide de 0x3f800000:

float MersenneFloat(float min, float max) 
{ 
    //genrand returns a double in [1,2) 
    const float random = (float)genrand_close1_open2(); 
    //return in desired range 
    return min + (random - 1.0f) * (max - min); 
} 

Si cela fait une différence, cela doit travailler sur Win32 MSVC++ et Linux gcc. En outre, l'utilisation de toutes les versions des optimisations SSE changera-t-elle la réponse?

Editer: La réponse est oui, la troncature dans ce cas du double au flottant est suffisante pour que le résultat inclue max. Voir la réponse de Crashworks pour plus.

Répondre

4

Les opérations ESS changeront subtilement le comportement de cet algorithme, car ils ne sont pas la représentation de 80 bits intermédiaire - le calcul est vraiment fait en 32 ou 64 bits. La bonne nouvelle est que vous pouvez facilement la tester et voir si elle change vos résultats en spécifiant simplement l'option de ligne de commande/ARCH: SSE2 à MSVC, ce qui l'amènera à utiliser les opérations scalaires SSE au lieu des instructions FPU x87 pour les virgules flottantes ordinaires math.

Je ne suis pas sûr de ce que le comportement d'arrondi exact est autour des limites entières, mais vous pouvez tester pour voir ce qui se passera quand 1.999 ..obtient arrondi de 64 à 32 bits par par exemple

static uint64 OnePointNineRepeating = 0x3FF FFFFF FFFF FFFF // exponent 0 (biased to 1023), all 1 bits in mantissa 
double asDouble = *(double *)(&OnePointNineRepeating); 
float asFloat = asDouble; 
return asFloat; 

Modifier, résultat: Affiche originale a couru ce test et a constaté que avec troncature, le 1,99999 va arrondir à 2 avec et sans/arc: SSE2 .

+0

Maintenant, pourquoi n'ai-je pas pensé à lancer ce test parmi les autres que j'ai courus :) J'ai découvert qu'avec la troncature, le 1.99999 arrondira à 2 avec et sans/arch: SSE2. Merci! –

+0

Heureux d'aider - J'étais curieux de savoir quel serait le résultat du test moi-même. – Crashworks

0

Si vous ajustez l'arrondi pour inclure les deux extrémités de la plage, ces valeurs extrêmes ne seront-elles pas deux fois moins élevées que celles des valeurs extrêmes?

+0

Il me semble que si j'utilise juste la troncature, la réponse est oui, mais si j'incrémente le significand max, la réponse serait non. –

0

Avec la troncature, vous n'allez jamais inclure le maximum.

Etes-vous sûr que vous avez vraiment besoin du maximum? Il y a littéralement presque 0 chances que vous atterrissiez exactement au maximum.

Cela dit, vous pouvez exploiter le fait que vous renoncez précision et faire quelque chose comme ceci:

float MersenneFloat(float min, float max) 
{ 
    double random = 100000.0; // just a dummy value 
    while ((float)random > 65535.0) 
    { 
     //genrand returns a double in [1,2) 
     double random = genrand_close1_open2() - 1.0; // now it's [0,1) 
     random *= 65536.0; // now it's [0,65536). We try again if it's > 65535.0 
    } 
    //return in desired range 
    return min + float(random/65535.0) * (max - min); 
} 

Notez que, maintenant, il a une petite chance de multiples appels à genrand chaque fois que vous appelez MersenneFloat. Vous avez donc abandonné les performances possibles pour un intervalle fermé. Puisque vous descendez du double au flottant, vous finissez par ne sacrifier aucune précision.

Edit: algorithme amélioré

+0

Oui, j'ai besoin que le maximum soit inclus (c'est un contrat de fonction de bibliothèque). Y aurait-il un avantage à le faire à votre façon, plutôt que d'augmenter le significand de ma valeur maximale avant la multiplication? –

+0

Cela peut fonctionner aussi bien. Quelque part, cependant, vous devrez soit faire un test de rejet, ou avoir une distribution imparfaite des valeurs. Un analogue de ce problème est, par exemple, la génération d'un entier 0-256 à partir d'un int aléatoire 0-65535. Cela ne correspond pas à la réalité. – rlbond

+0

En fait, j'ai juste essayé la suggestion de test de Crashworks, et la troncature est en fait arrondie. –

Questions connexes