[R] Imprécision addition nombres à virgule (float)

Aide et conseils concernant AutoIt et ses outils.
Règles du forum
.
Répondre
Asardex
Niveau 2
Niveau 2
Messages : 15
Enregistré le : mer. 14 janv. 2015 14:52
Status : Hors ligne

[R] Imprécision addition nombres à virgule (float)

#1

Message par Asardex »

Bonjour !

Je développe un utilitaire qui a besoin de faire des additions successives sur des nombres, parfois entiers, parfois flottants, en boucle.
Seulement voilà, comme je pouvais m'y attendre, j'ai un soucis avec certains calculs impliquant des nombres flottants, dont le résultat est proche d'un entier. C'est d'ailleurs le même problème qui est évoqué dans le dernier paragraphe des remarques dans l'aide de la fonction Int().

J'ai extrait ci dessous une partie de ma boucle pour reproduire "à la main" le comportement rencontré...
► Afficher le texte
J'ai besoin de pouvoir accumuler des nombres à virgule lors de chaque passage dans la boucle, sans qu'il y ait ce problème d’imprécision. C'est pourquoi je ne peux pas directement appliquer Int() à chaque fois.

Avez vous des suggestions pour résoudre mon problème ?
Comme quoi, certaines choses très simples comme des additions peuvent se révélé assez complexes :!:

Merci de votre aide par avance
Modifié en dernier par Asardex le ven. 15 mai 2020 16:05, modifié 1 fois.
Asardex
Niveau 2
Niveau 2
Messages : 15
Enregistré le : mer. 14 janv. 2015 14:52
Status : Hors ligne

Re: [..] Imprécision addition nombres à virgule (float)

#2

Message par Asardex »

J'ai trouvé une solution répondant à mon problème. En effet, je sais que les nombres à virgule présent dans mon set de données sont, au plus précis, à deux chiffres après la virgule (0,25 par exemple).
J'utilise donc la fonction Round() pour arrondir ma valeur calculée à deux décimales.
Je n'applique cette fonction qu'en fin de boucle, puisque les imprécisions créés durant la boucle ne permettront pas un mauvais arrondi à la fin (il n'y aura jamais assez de valeurs à additionner pour permettre une telle imprécision).

Voici le code que j'utilise :
► Afficher le texte
Donc $val = Round($val, 2) pour un arrondi de précision à deux chiffres après la virgule !


Juste pour pousser la chose plus loin ... Si j'avais eu besoin d'une précision à 20 chiffres (par exemple) après la virgule, et puisque l'imprécision générée par les calculs est de l'ordre de -4.44x10^-16, donc de 15/16 chiffres après la virgule, quelle(s) solution(s) pourrai(en)t répondre à ce problème ?
Avatar du membre
TommyDDR
Modérateur
Modérateur
Messages : 2086
Enregistré le : mar. 22 juil. 2008 21:55
Localisation : Nantes
Status : Hors ligne

Re: [..] Imprécision addition nombres à virgule (float)

#3

Message par TommyDDR »

J'ai aussi rencontré par mal de problème en additionnant en boucle plusieurs nombre à virgule.

Voilà ce que j'utilise quand je dois le faire maintenant
Cela consiste à regarder combien de chiffres il y a après la virgule dans le nombre qui en possède le plus, puis de multiplier les 2 nombres à additionner par 10 puissance (plus grand nombre de chiffres après la virgule), de les transformer en int (car plus rien après la virgule), de les aditionner et de retourner le résultat divisé par le même nombre qui les a multiplié.

Exemple :
1.5 + -2.25
plus grand nombre de décimales : 2 (-2.25 a 2 nombres après la virgule)
Multiplier les deux par 10^2 (=100)
150 + -225 = -75
Diviser le résultat par 100
-0.75
#include <Math.au3>

Local $val = 0
ConsoleWrite($val & @CRLF) ; initialisation : 0
$val += -12 * 0.2
$val += -2 * 3
$val += 10 * 1
$val +=  -2 * 1
ConsoleWrite($val & @CRLF) ; fin du premier passage dans la boucle : -0.4
$val += 15 * 0.2
$val += -3 * 1
ConsoleWrite($val & @CRLF) ; fin du deuxième passage dans la boucle : -0.4
$val += 3 * 1
$val += -1 * 3
ConsoleWrite($val & @CRLF) ; fin du troisième passage dans la boucle : -0.4
$val += -8 * 0.2
ConsoleWrite($val & @CRLF)
$val += -1.0 * 1.0
ConsoleWrite($val & @CRLF)
$val += 3.0
ConsoleWrite($val & @CRLF) ; fin du quatrième passage dans la boucle : -4.44x10^-16 <=> 0
;~ ; La boucle continue ses calculs, tout en conservant l'erreur ...

ConsoleWrite("----" & @CRLF)

Local $val2 = 0
$val2 = add($val2, -12 * 0.2)
$val2 = add($val2, -2 * 3)
$val2 = add($val2, 10 * 1)
$val2 = add($val2, -2 * 1)
ConsoleWrite($val2 & @CRLF) ; fin du premier passage dans la boucle : -0.4
$val2 = add($val2, 15 * 0.2)
$val2 = add($val2, -3 * 1)
ConsoleWrite($val2 & @CRLF) ; fin du deuxième passage dans la boucle : -0.4
$val2 = add($val2, 3 * 1)
$val2 = add($val2, -1 * 3)
ConsoleWrite($val2 & @CRLF) ; fin du troisième passage dans la boucle : -0.4
$val2 = add($val2, -8 * 0.2)
ConsoleWrite($val2 & @CRLF) ; fin du troisième passage dans la boucle : -0.4
$val2 = add($val2, -1.0 * 1.0)
ConsoleWrite($val2 & @CRLF) ; fin du troisième passage dans la boucle : -0.4
$val2 = add($val2, 3.0 * 1)
ConsoleWrite($val2 & @CRLF) ; fin du quatrième passage dans la boucle : 0


Func add($x1, $x2)
   Local $nbs = [$x1, $x2]
   Local $decimalPos[UBound($nbs)]
   Local $nbDecimal[UBound($nbs)]
   Local $nbDecimalMax = 0
   For $i = 0 To UBound($nbDecimal, 1) - 1
      $nbDecimal[$i] = 0
      $decimalPos[$i] = StringInStr($nbs[$i], ".")
      If($decimalPos[$i] > 0) Then
         $nbDecimal[$i] = StringLen($nbs[$i]) - $decimalPos[$i]
      EndIf
      $nbDecimalMax = _Max($nbDecimalMax, $nbDecimal[$i])
   Next
   Local $coef = 10^$nbDecimalMax
   Local $retour = 0
   For $i = 0 To UBound($nbs, 1) - 1
      $retour += Int($nbs[$i] * $coef)
   Next
   Return $retour / $coef
EndFunc
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679
Avatar du membre
Tlem
Site Admin
Site Admin
Messages : 11773
Enregistré le : ven. 20 juil. 2007 21:00
Localisation : Bordeaux
Status : Hors ligne

Re: [..] Imprécision addition nombres à virgule (float)

#4

Message par Tlem »

Bonsoir.
Pour infos, jchd avais déjà évoqué cela ^^ : https://www.autoitscript.fr/forum/viewt ... =3&t=11324

Par contre, est-ce que vraiment il est utile d'avoir une précision au delà de 15 décimale ?
Déjà deux je trouve que c'est bien. Mais j'avoue ne pas avoir en tête un seul exemple ou une précision supérieure serait nécessaire.
Un exemple d'utilité nous permettrait de comprendre. :D
Thierry

Rechercher sur le forum ----- Les règles du forum
Le "ça ne marche pas" est une conséquence commune découlant de beaucoup trop de raisons potentielles ...

Une idée ne peut pas appartenir à quelqu'un. (Albert Jacquard) tiré du documentaire "Copié n'est pas volé".
Avatar du membre
jchd
AutoIt MVPs (MVP)
AutoIt MVPs (MVP)
Messages : 2272
Enregistré le : lun. 30 mars 2009 22:57
Localisation : Sud-Ouest de la France (43.622788,-1.260864)
Status : Hors ligne

Re: [..] Imprécision addition nombres à virgule (float)

#5

Message par jchd »

Si j'avais eu besoin d'une précision à 20 chiffres (par exemple) après la virgule
Impossible avec des double sur 64 bit.
, et puisque l'imprécision générée par les calculs est de l'ordre de -4.44x10^-16
Non, "l'imprécision" (en fait l'erreur absolue) est de 1 ULP (Unit in the Last Place) autour de la valeur considérée.
, donc de 15/16 chiffres après la virgule, quelle(s) solution(s) pourrai(en)t répondre à ce problème ?
Trois possibilités :
-) utiliser une bibliothèque externe proposant des nombres flotants sur 128 bits ou plus,
https://en.wikipedia.org/wiki/Quadruple ... int_format
https://en.wikipedia.org/wiki/Octuple-p ... int_format
-) utiliser une bibliothèque externe proposant de l'arithmétique à précision non bornée,
https://en.wikipedia.org/wiki/List_of_a ... c_software
-) utiliser l'UDF BigNum.au3
-) utiliser un logiciel externe (GP-Pari, Mathematica, Mapple, ...)
Par exemple, Mathematica permet autant le calcul symbolique exact que l'approximation à n'importe quelle précision souhaitée :

In[1]:= calcul = Sqrt[Pi^E + Log[3]]
Out[1]= Sqrt[\[Pi]^E + Log[3]] ; l'expression du calcul n'est pas exacte, donc reste sous cette forme symbolique

In[2]:= N[calcul, 274] ; je demande la valeur approchée à 274 décimales exactes
Out[2]= 4.853634721219671179059680005795474316737483903940018893683896454643264387449702074438021320558052759306807759006544812558178881370521664698251890173227870708686470396833566794828442177178733001091006658209482320661184938490988795382611674035658581988313533316602515259987996

In[3]:= N[calcul + 10^40, 313] ; la précision ne dépend pas de l'amplitude de la valeur
Out[3]= 1.0000000000000000000000000000000000000004853634721219671179059680005795474316737483903940018893683896454643264387449702074438021320558052759306807759006544812558178881370521664698251890173227870708686470396833566794828442177178733001091006658209482320661184938490988795382611674035658581988313533316602515259987996*10^40
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.
Asardex
Niveau 2
Niveau 2
Messages : 15
Enregistré le : mer. 14 janv. 2015 14:52
Status : Hors ligne

Re: [..] Imprécision addition nombres à virgule (float)

#6

Message par Asardex »

Merci à vous tous pour les précisions que vous avez apporté ! :)
Je garde dans un coin la solution de TommyDDR, tout en ayant conscience qu'avec des nombres trop grands codés sur 64 bits ou moins, ça peut vite devenir source d'erreurs.
Je suis sûr qu'on pourrait trouver des cas qui auraient besoin d'une précision supérieure à 15 chiffres après la virgule, mais dans ces cas là, j'imagine facilement que d'autres langages seraient plus adaptés.
Merci aussi à jchd pour les précisions et possibilités apportées dans son message.

Je marque le sujet comme résolu
Bonne fin de journée à tous
Avatar du membre
TommyDDR
Modérateur
Modérateur
Messages : 2086
Enregistré le : mar. 22 juil. 2008 21:55
Localisation : Nantes
Status : Hors ligne

Re: [R] Imprécision addition nombres à virgule (float)

#7

Message par TommyDDR »

Oui, j'ai oublié de vous préciser que je l'utilisais pour une précision à 2 chiffres après la virgule et pour des totaux de l'ordre de 1000 (même si la solution dans ces conditions peut allez bien plus loin).
3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679
Avatar du membre
jchd
AutoIt MVPs (MVP)
AutoIt MVPs (MVP)
Messages : 2272
Enregistré le : lun. 30 mars 2009 22:57
Localisation : Sud-Ouest de la France (43.622788,-1.260864)
Status : Hors ligne

Re: [R] Imprécision addition nombres à virgule (float)

#8

Message par jchd »

Ceci dit, il faut toujours se méfier des caractéristiques parfois surprenantes des nombres en virgule flotante au cours d'un calcul (surtout en chaîne).
Je ne sais plus trop où j'ai déjà posté cet exemple, mais il illustre de façon éclatante qu'un calcul qui semble assez anodin peut complètement dégénérer. Ici on ne cherche pas une précision de folie mais seulement une valeur approchée de la limite d'une suite "simple". Et c'est là que ça devient drôle... ou pas !

https://www.autoitscript.com/forum/topi ... -be-toxic/
La cryptographie d'aujourd'hui c'est le taquin plus l'électricité.
Répondre