web-dev-qa-db-fra.com

L'arithmétique en virgule flottante est-elle stable?

Je sais que les nombres à virgule flottante ont une précision et les chiffres après la précision ne sont pas fiables.

Mais que se passe-t-il si l'équation utilisée pour calculer le nombre est la même? puis-je supposer que le résultat serait le même aussi?

par exemple, nous avons deux nombres flottants x et y. Pouvons-nous supposer le résultat x/y de la machine 1 est exactement le même que le résultat de la machine 2? C'EST À DIRE. == la comparaison retournerait vraie

36
Steve

Mais que se passe-t-il si l'équation utilisée pour calculer le nombre est la même? puis-je supposer que le résultat serait le même aussi?

Non pas forcément.

En particulier, dans certaines situations, le JIT est autorisé à utiliser une représentation intermédiaire plus précise - par ex. 80 bits lorsque vos données d'origine sont de 64 bits - contrairement à d'autres situations. Cela peut entraîner des résultats différents lorsque l'une des conditions suivantes est vraie:

  • Vous avez un code légèrement différent, par exemple en utilisant une variable locale au lieu d'un champ, ce qui peut changer si la valeur est stockée dans un registre ou non. (C'est un exemple relativement évident; il y en a d'autres beaucoup plus subtils qui peuvent affecter des choses, comme l'existence d'un bloc try dans la méthode ...)
  • Vous exécutez sur un processeur différent (j'avais l'habitude d'observer les différences entre AMD et Intel; il peut également y avoir des différences entre différents processeurs du même fabricant)
  • Vous exécutez avec différents niveaux d'optimisation (par exemple sous un débogueur ou non)

Dans la section 4.1.6 de la spécification C # 5:

Les opérations en virgule flottante peuvent être effectuées avec une précision supérieure au type de résultat de l'opération. Par exemple, certaines architectures matérielles prennent en charge un type à virgule flottante "étendu" ou "long double" avec une portée et une précision supérieures à celles du type double, et effectuent implicitement toutes les opérations à virgule flottante à l'aide de ce type de précision supérieure. Ce n'est qu'à un coût excessif en performances que de telles architectures matérielles peuvent être réalisées pour effectuer des opérations en virgule flottante avec moins de précision, et plutôt que d'exiger une implémentation pour perdre à la fois les performances et la précision, C # permet d'utiliser un type de précision plus élevée pour toutes les opérations en virgule flottante . En plus de fournir des résultats plus précis, cela a rarement des effets mesurables. Cependant, dans les expressions de la forme x * y / z, où la multiplication produit un résultat qui est en dehors de la plage double, mais la division suivante ramène le résultat temporaire dans la plage double, le fait que l'expression est évaluée dans un format de plage supérieure peut entraîner la production d'un résultat fini au lieu d'un infini.

45
Jon Skeet

La réponse de Jon est bien sûr correcte. Cependant, aucune des réponses n'a indiqué comment vous pouvez vous assurer que l'arithmétique à virgule flottante est effectuée dans la quantité de précision garantie par la spécification et pas plus .

C # tronque automatiquement tout flottant vers sa représentation canonique 32 ou 64 bits dans les circonstances suivantes:

  • Vous mettez dans une distribution explicite redondante: x + y peut avoir x et y comme nombres de précision supérieure qui sont ensuite ajoutés. Mais (double)((double)x+(double)y) garantit que tout est tronqué avec une précision de 64 bits avant et après le calcul.
  • Tout magasin dans un champ d'instance d'une classe , un champ statique, un élément de tableau ou un pointeur déréférencé est toujours tronqué. (Les magasins vers les locaux, les paramètres et les temporaires ne sont pas garantis à tronquer; ils peuvent être enregistrés. Les champs d'une structure peuvent être sur le pool à court terme qui peut également être enregistré.)

Ces garanties ne sont pas apportées par la spécification du langage, mais les implémentations doivent respecter ces règles. Les implémentations Microsoft de C # et du CLR le font.

Il est difficile d'écrire le code pour s'assurer que l'arithmétique à virgule flottante est prévisible en C # mais cela peut être fait. Notez que cela ralentira probablement votre arithmétique.

Les plaintes concernant cette terrible situation doivent être adressées à Intel, pas à Microsoft; ce sont eux qui ont conçu les puces qui rendent le calcul arithmétique plus lent.

Notez également qu'il s'agit d'une question fréquemment posée. Vous pourriez envisager de fermer ceci comme un doublon de:

Pourquoi diffère la précision en virgule flottante en C # lorsqu'ils sont séparés par des parenthèses et lorsqu'ils sont séparés par des instructions?

Pourquoi ce calcul en virgule flottante donne-t-il des résultats différents sur différentes machines?

Casting d'un résultat en float dans la méthode retournant float change le résultat

(. 1f + .2f ==. 3f)! = (.1f + .2f) .Equals (.3f) Pourquoi?

Contrainte à virgule flottante pour être déterministe dans .NET?

C # XNA Visual Studio: Différence entre les modes "release" et "debug"?

C # - Résultat d'opération mathématique incohérent sur 32 bits et 64 bits

Erreur d'arrondi en C #: Résultats différents sur différents PC

Comportement étrange du compilateur avec littéraux flottants vs variables flottantes

20
Eric Lippert

Non. Le résultat du calcul peut différer selon le processeur, car la mise en œuvre de l'arithmétique à virgule flottante peut différer selon le fabricant du processeur ou la conception du processeur. Je me souviens même d'un bug dans l'arithmétique à virgule flottante dans certains processeurs Intel, qui a foiré nos calculs.

Et puis il y a une différence dans la façon dont le code est évalué par le compilateur JIT.

9
Patrick Hofman

Franchement, je ne m'attendrais pas à ce que deux endroits dans la même base de code retournent la même chose pour x/y pour les mêmes x et y - dans le même processus sur la même machine; cela peut dépendre de la façon dont x et y sont optimisés par le compilateur/JIT - s'ils sont enregistrés différemment, ils peuvent avoir différentes précisions intermédiaires. De nombreuses opérations sont effectuées en utilisant plus de bits que prévu (taille de registre); et exactement quand cela est forcé à 64 bits peut affecter le résultat. Le choix de ce "exactement quand" peut dépendre de ce qui se passe dans le code environnant.

8
Marc Gravell

En théorie, sur un système conforme IEEE 754, la même opération avec les mêmes valeurs d'entrée est censée produire le même résultat.

Comme le résume Wikipedia:

L'IEEE 754-1985 a permis de nombreuses variations dans les implémentations (telles que l'encodage de certaines valeurs et la détection de certaines exceptions). IEEE 754-2008 en a renforcé un grand nombre, mais quelques variantes subsistent (en particulier pour les formats binaires). La clause de reproductibilité recommande que les normes linguistiques fournissent un moyen d'écrire des programmes reproductibles (c'est-à-dire des programmes qui produiront le même résultat dans toutes les implémentations d'un langage), et décrit ce qui doit être fait pour obtenir des résultats reproductibles.

Comme d'habitude, cependant, la théorie est différente de la pratique. La plupart des langages de programmation d'usage courant, C # inclus, ne sont pas strictement conformes à IEEE 754, et ne fournissent pas nécessairement un moyen d'écrire un programme reproductible.

De plus, les processeurs/FPU modernes rendent la gestion de la conformité IEEE 754 stricte. Par défaut, ils fonctionneront avec une "précision étendue", stockant les valeurs avec plus de bits qu'un double en interne. Si vous souhaitez une sémantique stricte, vous devez extraire les valeurs du FPU dans un registre CPU, rechercher et gérer diverses exceptions FPU, puis repousser les valeurs entre chaque opération FPU. En raison de cette maladresse, la conformité stricte a une pénalité de performance, même au niveau matériel. La norme C # a choisi une exigence plus "bâclée" pour éviter d'imposer une pénalité de performance au cas le plus courant où les petites variations ne sont pas un problème.

Rien de tout cela n'est souvent un problème dans la pratique, car la plupart des programmeurs ont internalisé l'idée (incorrecte ou au moins trompeuse) que les mathématiques à virgule flottante sont inexactes. De plus, les erreurs dont nous parlons ici sont toutes extrêmement petites, suffisamment pour qu'elles soient éclipsées par la perte de précision beaucoup plus courante causée par la conversion de la décimale.

3
Daniel Pryden

En plus des autres réponses, il est possible x/y != x/y même sur la même machine .

Les calculs à virgule flottante dans x86 sont effectués à l'aide de registres 80 bits, mais tronqués à 64 bits lorsqu'ils sont stockés. Il est donc possible que la première division soit calculée, tronquée, puis rechargée en mémoire et comparée à la division non tronquée.

Voir ici pour plus d'informations (c'est un lien C++, mais le raisonnement est le même)