Page 1 sur 1

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

Posté : jeu. 14 mai 2020 13:27
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

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

Posté : jeu. 14 mai 2020 14:31
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 ?

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

Posté : jeu. 14 mai 2020 14:57
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

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

Posté : jeu. 14 mai 2020 21:28
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

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

Posté : ven. 15 mai 2020 15:19
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

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

Posté : ven. 15 mai 2020 16:05
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

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

Posté : ven. 15 mai 2020 18:16
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).

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

Posté : ven. 15 mai 2020 19:25
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/