Comment gérer les excès de précision dans les calculs en virgule flottante?

Dans ma simulation numérique, j’ai un code similaire à l’extrait suivant

double x; do { x = /* some computation */; } while (x <= 0.0); /* some algorithm that requires x to be (precisely) larger than 0 */ 

Avec certains compilateurs (par exemple, gcc) sur certaines plates-formes (par exemple, linux, x87 math), il est possible que x soit calculé avec une précision supérieure à la double précision (“avec une précision excessive”). ( Mise à jour : lorsque je parle de précision ici, je veux dire précision / et / distance.) Dans ces circonstances, il est concevable que la comparaison ( x <= 0 ) renvoie false même si la prochaine fois que x est arrondi à la double précision, il devient 0. (Et rien ne garantit que x n’est pas arrondi à un moment arbitraire.)

Est-il possible d’effectuer cette comparaison

  • est portable,
  • fonctionne dans un code qui est en ligne,
  • n’a aucun impact sur les performances et
  • n’exclut pas une plage quelconque (0, eps)?

J’ai essayé d’utiliser ( x < std::numeric_limits::denorm_min() ), mais cela semblait ralentir considérablement la boucle lorsque je travaillais avec SSE2 Math. (Je sais que les effets normaux peuvent ralentir un calcul, mais je ne m’attendais pas à ce qu’ils soient plus lents à se déplacer et à comparer.)

Mise à jour: une alternative consiste à utiliser volatile pour forcer x en mémoire avant la comparaison, par exemple en écrivant

 } while (*((volatile double*)&x) <= 0.0); 

Toutefois, en fonction de l’application et des optimisations appliquées par le compilateur, cette solution peut également entraîner une surcharge sensible.

Mise à jour: Le problème avec toute tolérance est que c’est assez arbitraire, c’est-à-dire que cela dépend de l’application ou du contexte spécifique. Je préférerais simplement faire la comparaison sans excès de précision, afin de ne pas avoir à formuler d’hypothèses supplémentaires ni à introduire des epsilons arbitraires dans la documentation de mes fonctions de bibliothèque.

Comme Arkadiy l’a déclaré dans les commentaires, une conversion explicite ((double)x) <= 0.0 devrait fonctionner - du moins selon la norme.

C99: TC3, 5.2.4.2.2 §8:

Sauf pour les affectations et les transtypes (qui suppriment toute plage et toute précision supplémentaires), les valeurs des opérations avec des opérandes et valeurs soumis aux conversions arithmétiques habituelles et des constantes flottantes sont évaluées dans un format dont la plage et la précision peuvent être supérieures à celles requirejses par le paramètre type. [...]


Si vous utilisez GCC sur x86, vous pouvez utiliser les indicateurs -mpc32 , -mpc64 et -mpc80 pour définir la précision des opérations à virgule flottante sur simple, double et double précision étendue.

Dans votre question, vous avez indiqué que l’utilisation de la technologie volatile fonctionnerait, mais que les performances en souffriraient énormément. Qu’en est-il de l’utilisation de la variable volatile uniquement lors de la comparaison, permettant à x d’être conservé dans un registre?

 double x; /* might have excess precision */ volatile double x_dbl; /* guaranteed to be double precision */ do { x = /* some computation */; x_dbl = x; } while (x_dbl <= 0.0); 

Vous devez également vérifier si vous pouvez accélérer la comparaison avec la plus petite valeur sous-normale en utilisant explicitement long double et mettre en cache cette valeur, c.-à-d.

 const long double dbl_denorm_min = static_cast(std::numeric_limits::denorm_min()); 

puis comparer

 x < dbl_denorm_min 

Je suppose qu'un bon compilateur le ferait automatiquement, mais on ne sait jamais ...

Je me demande si vous avez le bon critère d’arrêt. Il semble que x <= 0 soit une condition d' exception , mais pas une condition de fin et que la condition de fin est plus facile à satisfaire. Il devrait peut-être y avoir une déclaration break dans votre boucle while qui arrête l’itération lorsque certaines tolérances sont respectées. Par exemple, de nombreux algorithmes se terminent lorsque deux itérations successives sont suffisamment proches l’une de l’autre.

Eh bien, GCC a un drapeau, -fexcess-precision qui cause le problème dont vous parlez. Il a également un drapeau, -ffloat-store, qui résout le problème dont vous parlez.

“Ne stockez pas les variables à virgule flottante dans des registres. Cela évite une précision excessive sur des machines telles que la 68000, où les registres flottants (de la 68881) conservent plus de précision que ce qu’un double est supposé avoir.”

Je doute que cette solution n’ait pas d’impact sur les performances, mais l’impact n’est probablement pas excessivement coûteux. Googling aléatoire suggère que cela coûte environ 20%. En fait, je ne pense pas qu’il existe une solution portable et n’ayant aucun impact sur les performances, car forcer une puce à ne pas utiliser une précision excessive implique souvent une opération non libre. Cependant, c’est probablement la solution que vous souhaitez.

Assurez-vous de faire de cette vérification une valeur absolue. Il doit être un epsilon autour de zéro, au-dessus et en dessous.