En créant une application qui traite de nombreux calculs mathématiques, j'ai rencontré le problème que certains nombres provoquent des erreurs d'arrondi.
Bien que je comprenne que la virgule flottante n'est pas exacte , le problème est de savoir comment gérer les nombres exacts pour m'assurer que lorsque les calculs sont effectués sur eux, l'arrondi en virgule flottante ne pose aucun problème?
distanceTraveled(startVel, duration, acceleration)
serait.Réponses:
Il existe trois approches fondamentales pour créer d'autres types numériques sans arrondi flottant. Le thème commun avec ceux-ci est qu'ils utilisent à la place des mathématiques entières de différentes manières.
Rationnels
Représentez le nombre dans sa totalité et un nombre rationnel avec un numérateur et un dénominateur. Le nombre
15.589
serait représenté parw: 15; n: 589; d:1000
.Lorsqu'il est ajouté à 0,25 (qui est
w: 0; n: 1; d: 4
), cela implique de calculer le LCM, puis d'ajouter les deux nombres. Cela fonctionne bien dans de nombreuses situations, mais peut entraîner de très grands nombres lorsque vous travaillez avec de nombreux nombres rationnels relativement premiers les uns par rapport aux autres.Un point fixe
Vous avez la partie entière et la partie décimale. Tous les nombres sont arrondis (il y a ce mot - mais vous savez où il se trouve) avec cette précision. Par exemple, vous pourriez avoir un point fixe avec 3 points décimaux.
15.589
+0.250
devient l'addition589 + 250 % 1000
pour la partie décimale (puis tout report à la partie entière). Cela fonctionne très bien avec les bases de données existantes. Comme mentionné, il y a un arrondi, mais vous savez où il se trouve et pouvez le spécifier de manière à ce qu'il soit plus précis que nécessaire (vous ne mesurez qu'à 3 décimales, alors faites-le fixe à 4).Virgule fixe flottante
Stockez une valeur et la précision.
15.589
est stocké comme15589
pour la valeur et3
pour la précision, tandis que0.25
est stocké comme25
et2
. Cela peut gérer une précision arbitraire. Je crois que c'est ce que les internes des utilisations BigDecimal de Java (ne l'ont pas examiné récemment) utilisent. À un moment donné, vous souhaiterez le retirer de ce format et l'afficher - et cela peut impliquer l'arrondi (encore une fois, vous contrôlez où il se trouve).Une fois que vous avez déterminé le choix de la représentation, vous pouvez soit trouver des bibliothèques tierces existantes qui l'utilisent, soit écrire les vôtres. Lorsque vous écrivez le vôtre, assurez-vous de le tester à l'unité et assurez-vous de bien faire les calculs.
la source
Si les valeurs en virgule flottante ont des problèmes d'arrondi et que vous ne voulez pas avoir à rencontrer de problèmes d'arrondi, il s'ensuit logiquement que la seule solution consiste à ne pas utiliser de valeurs en virgule flottante.
Maintenant, la question devient, "comment puis-je faire des mathématiques impliquant des valeurs non entières sans variables à virgule flottante?" La réponse est avec des types de données de précision arbitraire . Les calculs sont plus lents car ils doivent être implémentés dans le logiciel plutôt que dans le matériel, mais ils sont précis. Vous n'avez pas dit quel langage vous utilisez, donc je ne peux pas recommander un package, mais il existe des bibliothèques de précision arbitraires disponibles pour les langages de programmation les plus populaires.
la source
lot of mathematical calculations
n'est pas utile ni donner les réponses. Dans la grande majorité des cas (si vous ne traitez pas avec des devises), le flotteur devrait vraiment suffire.L'arithmétique en virgule flottante est généralement assez précise (15 chiffres décimaux pour a
double
) et assez flexible. Les problèmes surgissent lorsque vous faites des calculs, ce qui réduit considérablement le nombre de chiffres de précision. Voici quelques exemples:Annulation à la soustraction:, le
1234567890.12345 - 1234567890.12300
résultat0.0045
n'a que deux chiffres décimaux de précision. Cela se produit chaque fois que vous soustrayez deux nombres de magnitude similaire.Avalage de précision:
1234567890.12345 + 0.123456789012345
évalue à1234567890.24691
, les dix derniers chiffres du deuxième opérande sont perdus.Multiplications: si vous multipliez deux nombres à 15 chiffres, le résultat contient 30 chiffres qui doivent être stockés. Mais vous ne pouvez pas les stocker, donc les 15 derniers bits sont perdus. Ceci est particulièrement gênant lorsqu'il est combiné avec un
sqrt()
(comme danssqrt(x*x + y*y)
: Le résultat n'aura que 7,5 chiffres de précision.Ce sont les principaux pièges dont vous devez être conscient. Et une fois que vous en êtes conscient, vous pouvez essayer de formuler vos mathématiques d'une manière qui les évite. Par exemple, si vous devez incrémenter une valeur encore et encore dans une boucle, évitez de faire ceci:
Après quelques itérations, la plus grande
f
avalera une partie de la précision dedf
. Pire encore, les erreurs s'additionneront, conduisant à la situation contre-intuitive qu'un plus petitdf
peut conduire à de moins bons résultats globaux. Mieux vaut écrire ceci:Étant donné que vous combinez les incréments en une seule multiplication, le résultat
f
sera précis à 15 chiffres décimaux.Ceci n'est qu'un exemple, il existe d'autres façons d'éviter la perte de précision pour d'autres raisons. Mais cela aide déjà beaucoup à réfléchir à l'ampleur des valeurs impliquées et à imaginer ce qui se passerait si vous faisiez vos calculs avec un stylo et du papier, en arrondissant à un nombre fixe de chiffres après chaque étape.
la source
Comment vous assurer que vous n'avez pas de problèmes: renseignez-vous sur les problèmes d'arithmétique à virgule flottante, ou embauchez quelqu'un qui en a, ou utilisez votre bon sens.
Le premier problème est la précision. Dans de nombreuses langues, vous avez "float" et "double" (double pour "double precision"), et dans de nombreux cas, "float" vous donne une précision d'environ 7 chiffres, tandis que double vous en donne 15. Le bon sens est que si vous avez un situation où la précision pourrait être un problème, 15 chiffres est bien mieux que 7 chiffres. Dans de nombreuses situations légèrement problématiques, l'utilisation de "double" signifie que vous vous en sortez, et "float" signifie que vous ne le faites pas. Disons que la capitalisation boursière d'une entreprise est de 700 milliards de dollars. Représentez cela en float, et le bit le plus bas est 65536 $. Représentez-le en double, et le bit le plus bas est d'environ 0,012 cent. Donc, à moins que vous ne sachiez vraiment, vraiment ce que vous faites, vous utilisez double, pas flottant.
Le deuxième problème est davantage une question de principe. Si vous effectuez deux calculs différents qui devraient donner le même résultat, ils ne le font souvent pas en raison d'erreurs d'arrondi. Deux résultats qui devraient être égaux seront "presque égaux". Si deux résultats sont proches, les valeurs réelles peuvent être égales. Ou ils pourraient ne pas l'être. Vous devez garder cela à l'esprit et devez écrire et utiliser des fonctions qui disent que "x est certainement supérieur à y" ou "x est certainement inférieur à y" ou "x et y peuvent être égaux".
Ce problème devient encore pire si vous utilisez l'arrondi, par exemple "arrondir x à l'entier le plus proche". Si vous multipliez 120 * 0,05, le résultat devrait être 6, mais ce que vous obtenez est "un nombre très proche de 6". Si vous "arrondissez à l'entier le plus proche", ce "nombre très proche de 6" peut être "légèrement inférieur à 6" et arrondi à 5. Et notez que la précision dont vous disposez n'a pas d'importance. Peu importe à quel point votre résultat est proche de 6, tant qu'il est inférieur à 6.
Et troisièmement, certains problèmes sont difficiles . Cela signifie qu'il n'y a pas de règle simple et rapide. Si votre compilateur prend en charge "long double" avec plus de précision, vous pouvez utiliser "long double" et voir si cela fait une différence. Si cela ne fait aucune différence, alors soit vous êtes OK, soit vous avez un vrai problème délicat. Si cela fait le genre de différence que vous attendez (comme un changement à la 12e décimale), alors vous êtes probablement d'accord. Si cela change vraiment vos résultats, vous avez un problème. Demander de l'aide.
la source
La plupart des gens font l'erreur quand ils voient double, ils crient BigDecimal, alors qu'en fait ils viennent de déplacer le problème ailleurs. Double donne Bit de signe: 1 bit, Largeur d'exposant: 11 bits. Précision significative: 53 bits (52 explicitement stockés). En raison de la nature du double, plus le nombre entier est grand, plus vous perdez de précision relative. Pour calculer la précision relative que nous utilisons ici est ci-dessous.
Précision relative du double dans le calcul, nous utilisons la foluma suivante 2 ^ E <= abs (X) <2 ^ (E + 1)
epsilon = 2 ^ (E-10)% Pour un flottant de 16 bits (demi-précision)
En d'autres termes, si vous souhaitez une précision de +/- 0,5 (ou 2 ^ -1), la taille maximale que le nombre peut être est de 2 ^ 52. Tout plus grand que cela et la distance entre les nombres à virgule flottante est supérieure à 0,5.
Si vous voulez une précision de +/- 0,0005 (environ 2 ^ -11), la taille maximale que le nombre peut être est 2 ^ 42. Tout plus grand que cela et la distance entre les nombres à virgule flottante est supérieure à 0,0005.
Je ne peux pas vraiment donner une meilleure réponse que cela. L'utilisateur devra déterminer la précision qu'il souhaite lors de l'exécution du calcul nécessaire et sa valeur unitaire (mètres, pieds, pouces, mm, cm). Pour la grande majorité des cas, float suffira pour des simulations simples selon l'échelle du monde que vous souhaitez simuler.
Bien que ce soit quelque chose à dire, si vous ne cherchez qu'à simuler un monde de 100 mètres par 100 mètres, vous aurez quelque part dans l'ordre de précision près de 2 ^ -45. Cela n'entre même pas dans la façon dont les FPU modernes à l'intérieur des processeurs feront des calculs en dehors de la taille du type natif et seulement une fois le calcul terminé, ils arrondiront (en fonction du mode d'arrondi du FPU) à la taille du type natif.
la source