L'incompréhension de l'arithmétique à virgule flottante et de ses défauts est une cause majeure de surprise et de confusion dans la programmation (considérez le nombre de questions sur Stack Overflow concernant "les nombres ne s'ajoutent pas correctement"). Étant donné que de nombreux programmeurs n'ont pas encore compris ses implications, il a le potentiel d'introduire de nombreux bogues subtils (en particulier dans les logiciels financiers). Que peuvent faire les langages de programmation pour éviter ses pièges pour ceux qui ne connaissent pas les concepts, tout en offrant sa vitesse lorsque la précision est pas critique pour ceux qui ne comprennent les concepts?
language-design
Adam Paynter
la source
la source
Réponses:
Vous dites "spécialement pour les logiciels financiers", ce qui évoque une de mes bêtes noires: l' argent n'est pas un flotteur, c'est un int .
Bien sûr, cela ressemble à un flotteur. Il contient un point décimal. Mais c'est simplement parce que vous êtes habitué aux unités qui confondent le problème. L'argent vient toujours en quantités entières. En Amérique, c'est des cents. (Dans certains contextes, je pense que ce peut être des moulins , mais ignorez cela pour l'instant.)
Donc, quand vous dites 1,23 $, c'est vraiment 123 cents. Toujours, toujours, faites toujours vos calculs en ces termes, et tout ira bien. Pour plus d'informations, voir:
Pour répondre directement à la question, les langages de programmation devraient simplement inclure un type Money comme primitive raisonnable.
mise à jour
D'accord, j'aurais dû dire "toujours" deux fois plutôt que trois fois. L'argent est en effet toujours un int; ceux qui pensent le contraire sont invités à m'envoyer 0,3 centime et à me montrer le résultat sur votre relevé bancaire. Mais comme le soulignent les commentateurs, il existe de rares exceptions lorsque vous devez effectuer des calculs à virgule flottante sur des nombres de type monétaire. Par exemple, certains types de prix ou de calcul des intérêts. Même alors, ceux-ci devraient être traités comme des exceptions. L'argent entre et sort sous forme de quantités entières, donc plus votre système s'en rapproche, plus il sera sain.
la source
Decimal
est le seul système sensé pour faire face à cela, et votre commentaire "ignorer cela pour l'instant" est le signe avant-coureur du destin pour les programmeurs du mondeLa prise en charge d'un type décimal est utile dans de nombreux cas. De nombreuses langues ont un type décimal, mais elles sont sous-utilisées.
Il est important de comprendre l'approximation qui se produit lorsque vous travaillez avec la représentation de nombres réels. L'utilisation de types décimaux et à virgule flottante
9 * (1/9) != 1
est une instruction correcte. Lorsque les constantes, un optimiseur peut optimiser le calcul afin qu'il soit correct.Fournir un opérateur approximatif serait utile. Cependant, ces comparaisons sont problématiques. Notez que .9999 billion de dollars est approximativement égal à 1 billion de dollars. Pourriez-vous s'il vous plaît déposer la différence sur mon compte bancaire?
la source
0.9999...
mille milliards de dollars équivaut précisément à mille milliards de dollars.0.99999...
. Ils sont tous tronqués à un moment donné, ce qui entraîne une inégalité.0.9999
est suffisamment égal pour l'ingénierie. Pour des raisons financières, ce n'est pas le cas.On nous a dit quoi faire lors de la première année (deuxième année) de cours en informatique lorsque je suis allé à l'université (ce cours était également un pré-requis pour la plupart des cours de sciences)
Je me souviens du conférencier disant "Les nombres à virgule flottante sont des approximations. Utilisez des types entiers pour de l'argent. Utilisez FORTRAN ou une autre langue avec des nombres BCD pour un calcul précis." (puis il a souligné l'approximation, en utilisant cet exemple classique de 0,2 impossible à représenter avec précision en virgule flottante binaire). Cela est également apparu cette semaine dans les exercices de laboratoire.
Même cours: "Si vous devez obtenir plus de précision à partir de virgule flottante, triez vos termes. Ajoutez des petits nombres ensemble, pas des grands nombres." Cela m'est resté dans l'esprit.
Il y a quelques années, j'avais une géométrie sphérique qui devait être très précise et toujours rapide. Le double de 80 bits sur les PC ne le coupait pas, j'ai donc ajouté quelques types au programme qui triaient les termes avant d'effectuer des opérations commutatives. Problème résolu.
Avant de vous plaindre de la qualité de la guitare, apprenez à jouer.
J'ai eu un collègue il y a quatre ans qui avait travaillé pour JPL. Il a exprimé son incrédulité que nous avons utilisé FORTRAN pour certaines choses. (Nous avions besoin de simulations numériques super précises calculées hors ligne.) "Nous avons remplacé tout ce FORTRAN par du C ++", a-t-il déclaré fièrement. J'ai arrêté de me demander pourquoi ils avaient raté une planète.
la source
1.0 + 0.1 + ... + 0.1
(répété 10 fois) revient1.0
lorsque chaque résultat intermédiaire est arrondi. Faire l'inverse, vous obtenez des résultats intermédiaires0.2
,0.3
...,1.0
et enfin2.0
. C'est un exemple extrême, mais avec des nombres à virgule flottante réalistes, des problèmes similaires se produisent. L'idée de base est que l'ajout de nombres de taille similaire entraîne la plus petite erreur. Commencez avec les plus petits nombres car leur somme est plus grande et donc mieux adaptée pour être ajoutée à de plus grands.Je ne crois pas que quelque chose puisse ou doive être fait au niveau linguistique.
la source
Decimal
lorsqu'il s'agit de tests d'égalité. La différence entre1.0m/7.0m*7.0m
et1.0m
peut être de plusieurs ordres de grandeur inférieure à la différence entre1.0/7.0*7.0
, mais elle n'est pas nulle.Par défaut, les langues doivent utiliser des logiques de précision arbitraire pour les nombres non entiers.
Ceux qui ont besoin d'optimiser peuvent toujours demander des flotteurs. Les utiliser par défaut était logique dans C et d'autres langages de programmation de systèmes, mais pas dans la plupart des langages populaires aujourd'hui.
la source
double
. Si un calcul doit être précis à une partie par million, il est préférable de passer une microseconde à le calculer à quelques parties par milliard plutôt que de passer une seconde à le calculer de façon absolument précise.Les deux plus gros problèmes concernant les nombres à virgule flottante sont:
Le premier type de défaillance ne peut être résolu qu'en fournissant un type composite qui inclut des informations de valeur et d'unité. Par exemple, une valeur
length
ouarea
qui incorpore l'unité (mètres ou mètres carrés ou pieds et pieds carrés respectivement). Sinon, vous devez faire preuve de diligence pour toujours travailler avec un type d'unité de mesure et ne convertir en un autre que lorsque nous partageons la réponse avec un humain.Le deuxième type d'échec est un échec conceptuel. Les échecs se manifestent lorsque les gens les considèrent comme des nombres absolus . Elle affecte les opérations d'égalité, les erreurs d'arrondi cumulées, etc. Par exemple, il peut être correct que pour un système deux mesures soient équivalentes dans une certaine marge d'erreur. C'est à dire .999 et 1.001 sont à peu près les mêmes que 1.0 lorsque vous ne vous souciez pas des différences inférieures à +/- 0,1. Cependant, tous les systèmes ne sont pas aussi indulgents.
S'il y a une facilité de niveau de langue nécessaire, je l'appellerais précision d'égalité . Dans NUnit, JUnit et les frameworks de test construits de manière similaire, vous pouvez contrôler la précision considérée comme correcte. Par exemple:
Si, par exemple, C # ou Java ont été modifiés pour inclure un opérateur de précision, cela pourrait ressembler à ceci:
Cependant, si vous fournissez une fonctionnalité comme celle-ci, vous devez également considérer le cas où l'égalité est bonne si les côtés +/- ne sont pas les mêmes. Par exemple, + 1 / -10 considérerait deux nombres équivalents si l'un d'eux était à 1 de plus ou 10 de moins que le premier nombre. Pour gérer ce cas, vous devrez peut-être également ajouter un
range
mot clé:la source
Que peuvent faire les langages de programmation? Je ne sais pas s'il y a une réponse à cette question, car tout ce que le compilateur / interprète fait au nom du programmeur pour lui faciliter la vie va généralement à l'encontre des performances, de la clarté et de la lisibilité. Je pense que la méthode C ++ (ne payez que ce dont vous avez besoin) et la méthode Perl (principe de la moindre surprise) sont toutes deux valides, mais cela dépend de l'application.
Les programmeurs doivent encore travailler avec le langage et comprendre comment il gère les virgules flottantes, car s'ils ne le font pas, ils formuleront des hypothèses et un jour, le comportement préconisé ne correspondra pas à leurs hypothèses.
Mon point de vue sur ce que le programmeur doit savoir:
la source
Utilisez des valeurs par défaut raisonnables, par exemple un support intégré pour les decmials.
Groovy le fait très bien, mais avec un peu d'effort, vous pouvez toujours écrire du code pour introduire une imprécision en virgule flottante.
la source
Je suis d'accord qu'il n'y a rien à faire au niveau linguistique. Les programmeurs doivent comprendre que les ordinateurs sont discrets et limités, et que bon nombre des concepts mathématiques qui y sont représentés ne sont que des approximations.
Peu importe la virgule flottante. Il faut comprendre que la moitié des modèles de bits sont utilisés pour des nombres négatifs et que 2 ^ 64 est en fait assez petit pour éviter les problèmes typiques avec l'arithmétique entière.
la source
x
==y
n'implique pas que l'exécution d'un calcul surx
donnera le même résultat que l'exécution du même calcul sury
).Une chose que les langages pourraient faire - supprimer la comparaison d'égalité des types à virgule flottante autre qu'une comparaison directe aux valeurs NAN.
Le test d'égalité n'existerait que comme appel de fonction qui a pris les deux valeurs et un delta, ou pour des langages comme C # qui permettent aux types d'avoir des méthodes un EqualsTo qui prend l'autre valeur et le delta.
la source
Je trouve étrange que personne n'ait souligné le numéro rationnel de la famille Lisp.
Sérieusement, ouvrez sbcl, et faites ceci:
(+ 1 3)
et vous obtenez 4. Si vous le faites,*( 3 2)
vous obtenez 6. Maintenant, essayez(/ 5 3)
et vous obtenez 5/3, ou 5 tiers.Cela devrait aider quelque peu dans certaines situations, n'est-ce pas?
la source
Une chose que je voudrais voir serait une reconnaissance du fait que
double
defloat
devrait être considérée comme une conversion élargissement, alors quefloat
pourdouble
se rétrécit (*). Cela peut sembler contre-intuitif, mais réfléchissez à la signification réelle des types:Si l'on a un
double
qui détient la meilleure représentation de la quantité "un dixième" et le convertit enfloat
, le résultat sera "13 421 773,5 / 134 217 728, plus ou moins 1/268 435 456 environ", qui est une description correcte de la valeur.En revanche, si l'on a un
float
qui détient la meilleure représentation de la quantité "un dixième" et le convertit endouble
, le résultat sera "13 421 773,5 / 134 217 728, plus ou moins 1/72 057 594 097 927 936 environ" - un niveau de précision implicite ce qui est faux d'un facteur de plus de 53 millions.Bien que la norme IEEE-744 exige que les calculs à virgule flottante soient effectués comme si chaque nombre à virgule flottante représente la quantité numérique exacte précisément au centre de sa plage, cela ne devrait pas être interprété comme impliquant que les valeurs à virgule flottante représentent réellement ces valeurs exactes. quantités numériques. Au contraire, l'exigence selon laquelle les valeurs doivent être supposées être au centre de leurs plages découle de trois faits: (1) les calculs doivent être effectués comme si les opérandes avaient des valeurs précises particulières; (2) des hypothèses cohérentes et documentées sont plus utiles que des hypothèses incohérentes ou non documentées; (3) si l'on veut faire une hypothèse cohérente, aucune autre hypothèse cohérente n'est susceptible d'être meilleure que de supposer qu'une quantité représente le centre de sa fourchette.
Soit dit en passant, je me souviens il y a environ 25 ans, quelqu'un est venu avec un paquet numérique pour C qui utilisait des "types de plage", chacun consistant en une paire de flottants de 128 bits; tous les calculs seraient effectués de manière à calculer la valeur minimale et maximale possible pour chaque résultat. Si l'on effectuait un grand calcul itératif long et arrivait à une valeur de [12.53401391134 12.53902812673], on pouvait être sûr que même si de nombreux chiffres de précision étaient perdus en raison d'erreurs d'arrondi, le résultat pouvait toujours être raisonnablement exprimé comme 12,54 (et ce n'était pas le cas '' t vraiment 12,9 ou 53,2). Je suis surpris de n'avoir vu aucun support pour de tels types dans les langages traditionnels, d'autant plus qu'ils semblent convenir aux unités mathématiques qui peuvent fonctionner sur plusieurs valeurs en parallèle.
(*) Dans la pratique, il est souvent utile d'utiliser des valeurs à double précision pour effectuer des calculs intermédiaires lorsque vous travaillez avec des nombres à simple précision, donc avoir à utiliser un transtypage pour toutes ces opérations peut être ennuyeux. Les langues pourraient aider en ayant un type "double flou", qui effectuerait des calculs en double, et pourrait être librement casté vers et depuis un simple; cela serait particulièrement utile si les fonctions qui prennent des paramètres de type
double
et de retourdouble
pouvaient être marquées de manière à générer automatiquement une surcharge qui accepte et renvoie à la place un "double flou".la source
Si davantage de langages de programmation prenaient une page des bases de données et permettaient aux développeurs de spécifier la longueur et la précision de leurs types de données numériques, ils pourraient réduire considérablement la probabilité d'erreurs liées aux virgules flottantes. Si un langage permettait à un développeur de déclarer une variable comme un flottant (2), indiquant qu'il avait besoin d'un nombre à virgule flottante avec deux chiffres décimaux de précision, il pourrait effectuer des opérations mathématiques beaucoup plus en toute sécurité. S'il le faisait en représentant la variable sous forme d'entier en interne et en divisant par 100 avant d'exposer la valeur, il pourrait améliorer la vitesse en utilisant les chemins arithmétiques d'entier plus rapides. La sémantique d'un Float (2) permettrait également aux développeurs d'éviter le besoin constant d'arrondir les données avant de les produire car un Float (2) arrondirait intrinsèquement les données à deux décimales.
Bien sûr, vous devez autoriser un développeur à demander une valeur à virgule flottante de précision maximale lorsque le développeur doit avoir cette précision. Et vous introduiriez des problèmes où des expressions légèrement différentes de la même opération mathématique produisent des résultats potentiellement différents en raison d'opérations d'arrondi intermédiaires lorsque les développeurs n'ont pas assez de précision dans leurs variables. Mais au moins dans le monde des bases de données, cela ne semble pas être un problème trop important. La plupart des gens ne font pas le genre de calculs scientifiques qui nécessitent beaucoup de précision dans les résultats intermédiaires.
la source
Float(2)
comme vous proposez ne doit pas être appeléFloat
, car il n'y a rien flottant ici, certainement pas le "point décimal".Celles-ci sont applicables dans certains cas, mais pas vraiment une solution générale pour traiter les valeurs flottantes. La vraie solution est de comprendre le problème et d'apprendre à y faire face. Si vous utilisez des calculs de virgule flottante, vous devez toujours vérifier si vos algorithmes sont numériquement stables . Il existe un vaste domaine de mathématiques / informatique qui se rapporte au problème. C'est ce qu'on appelle l' analyse numérique .
la source
Comme d'autres réponses l'ont noté, le seul véritable moyen d'éviter les pièges à virgule flottante dans les logiciels financiers est de ne pas les utiliser là-bas. Cela peut être possible - si vous fournissez une bibliothèque bien conçue dédiée aux mathématiques financières .
Les fonctions conçues pour importer des estimations en virgule flottante doivent être clairement étiquetées comme telles et dotées de paramètres appropriés à cette opération, par exemple:
La seule véritable façon d'éviter les pièges à virgule flottante en général est l'éducation - les programmeurs doivent lire et comprendre quelque chose comme ce que chaque programmeur devrait savoir sur l'arithmétique à virgule flottante .
Quelques choses qui pourraient aider, cependant:
isNear()
fonction.la source
La plupart des programmeurs seraient surpris que COBOL ait bien compris ... dans la première version de COBOL, il n'y avait pas de virgule flottante, seulement décimale, et la tradition dans COBOL a continué jusqu'à aujourd'hui que la première chose à laquelle vous pensez lorsque vous déclarez un nombre est décimal. .. la virgule flottante ne serait utilisée que si vous en aviez vraiment besoin. Lorsque C est arrivé, pour une raison quelconque, il n'y avait pas de type décimal primitif, donc à mon avis, c'est là que tous les problèmes ont commencé.
la source