Laissez x
, y
deux nombres à virgule flottante. Quelle est la bonne façon de calculer leur moyenne?
La façon naïve (x+y)/2
peut entraîner des refoulements quand x
et y
sont trop grandes. Je pense que c'est 0.5 * x + 0.5 * y
peut-être mieux, mais cela implique deux multiplications (ce qui est peut-être inefficace), et je ne sais pas si c'est assez bon. Y a-t-il une meilleure façon?
Une autre idée avec laquelle je joue est de savoir (y/2)(1 + x/y)
si x<=y
. Mais encore une fois, je ne sais pas comment analyser cela et prouver qu'il répond à mes exigences.
De plus, j'ai besoin d'une garantie que la moyenne calculée sera >= min(x,y)
et <= max(x,y)
. Comme indiqué dans la réponse de Don Hatch , une meilleure façon de poser cette question est peut-être la suivante: quelle est l'implémentation de la moyenne de deux nombres qui donne toujours le résultat le plus précis possible? Autrement dit, si x
et y
sont des nombres à virgule flottante, comment calculer le nombre à virgule flottante le plus proche de (x+y)/2
? Dans ce cas, la moyenne calculée est automatiquement >= min(x,y)
et <= max(x,y)
. Voir la réponse de Don Hatch pour plus de détails.
Remarque: Ma priorité est une précision robuste. L'efficacité est consommable. Cependant, s'il existe de nombreux algorithmes robustes et précis, je choisirais le plus efficace.
la source
Réponses:
Je pense que la précision et la stabilité des algorithmes numériques de Higham explique comment analyser ces types de problèmes. Voir le chapitre 2, en particulier l'exercice 2.8.
Dans cette réponse, je voudrais souligner quelque chose qui n'est pas vraiment abordé dans le livre de Higham (il ne semble pas être très largement connu, d'ailleurs). Si vous souhaitez prouver les propriétés d'algorithmes numériques simples comme ceux-ci, vous pouvez utiliser la puissance des solveurs SMT modernes ( Satisfiability Modulo Theories ), tels que z3 , en utilisant un package tel que sbv dans Haskell. C'est un peu plus facile que d'utiliser du crayon et du papier.
Supposons que l'on me donne , et j'aimerais savoir si z = ( x + y ) / 2 satisfait x ≤ z ≤ y . Le code Haskell suivant0 ≤ x ≤ y z= ( x + y) / 2 x ≤ z≤y
me permettra de le faire automatiquement . Voicix ≤ f u n(x,y) ≤y x , y 0 ≤ x ≤ y
test1 fun
la proposition que pour tous les flotteurs finis x , y avec 0 ≤ x ≤ y .Il déborde. Supposons que je prenne maintenant votre autre formule:z=x/2+y/2
Ne fonctionne pas (en raison d'un débordement progressif: , ce qui pourrait ne pas être intuitif en raison de l'arithmétique de base 2).(x/2)×2≠x
Essayez maintenant :z=x+(y−x)/2
Travaux! Le
Q.E.D.
est une preuve que latest1
propriété est valable pour tous les flottants tels que définis ci-dessus.Qu'en est-il de la même chose, mais limité à (au lieu de 0 ≤ x ≤ y )?x≤y 0≤x≤y
D'accord, donc si déborde, qu'en est-il de z = x + ( y / 2 - x / 2 ) ?y−x z=x+(y/2−x/2)
Il semble donc que parmi les formules que j'ai essayées ici, semble fonctionner (avec une preuve aussi). L'approche du solveur SMT me semble un moyen beaucoup plus rapide de répondre aux soupçons sur les formules simples à virgule flottante que de passer par l'analyse d'erreurs à virgule flottante avec un crayon et du papier.x+(y/ 2-x /2)
Enfin, l'objectif de précision et de stabilité est souvent en contradiction avec l'objectif de performance. Pour les performances, je ne vois pas vraiment comment vous pouvez faire mieux que , d'autant plus que le compilateur fera toujours le gros du travail pour traduire cela en instructions machine pour vous.( x + y) / 2
SFloat
SDouble
-ffast-math
PPPS Je me suis un peu emporté en ne regardant que les expressions algébriques simples sans conditions. La formule de Don Hatch est strictement meilleure.
la source
>>> x = -1.; y = 1.+2.**-52; print `2**-53`, `(x+y)/2.`, `x+(y/2.-x/2.)`
Tout d'abord, observez que si vous avez une méthode qui donne une réponse la plus précise dans tous les cas, elle satisfera à votre condition requise. (Notez que je dis une réponse la plus exacte plutôt que la réponse la plus précise, car il peut y avoir deux gagnants.) Preuve: si, au contraire, vous avez une réponse aussi précise que possible qui ne remplit pas la condition requise, que signifie soit
answer<min(x,y)<=max(x,y)
(dans ce cas,min(x,y)
c'est une meilleure réponse, une contradiction), soitmin(x,y)<=max(x,y)<answer
(dans ce cas,max(x,y)
c'est une meilleure réponse, une contradiction).Je pense donc que cela signifie que votre question se résume à trouver la réponse la plus précise possible. En supposant l'arithmétique IEEE754 tout au long, je propose ce qui suit:
Mon argument selon lequel cela donne une réponse plus précise est une analyse de cas quelque peu fastidieuse. Voici:
Cas
max(abs(x),abs(y)) >= 1.
:x/2.+y/2.
manipule les mêmes mantisses et donne donc exactement la même réponse que le calcul de(x+y)/2
donnerait si nous supposions des exposants étendus pour empêcher le débordement. Cette réponse peut dépendre du mode d'arrondi mais dans tous les cas, elle est garantie par IEEE754 pour être la meilleure réponse possible (du fait que le calculx+y
est garanti comme étant la meilleure approximation de x + y mathématique, et la division par 2 est exacte dans ce cas). Cas).Le sous-cas x est dénormalisé (et ainsi
abs(y)>=1
):answer = x/2. + y/2. = y/2. since abs(x/2.) is so tiny compared to abs(y/2.) = the exact mathematical value of y/2 = a best possible answer.
La sous-case y est dénormalisée (et ainsi
abs(x)>=1
): analogue.max(abs(x),abs(y)) < 1.
:x+y
est soit non dénormalisé soit dénormalisé et "pair": bien que le calculx+y
ne soit pas exact, il est garanti par IEEE754 comme étant la meilleure approximation possible de la mathématique x + y. Dans ce cas, la division suivante par 2 dans l'expression(x+y)/2.
est exacte, donc la réponse calculée(x+y)/2.
est la meilleure approximation possible de la mathématique (x + y) / 2.x+y
est dénormalisé et "impair": dans ce cas, exactement l'un des x, y doit également être dénormalisé et "impair", ce qui signifie que l'autre de x, y est dénormalisé avec le signe opposé, et donc le calculx+y
est exactement le mathématique x + y, et donc le calcul(x+y)/2.
est garanti par IEEE754 comme étant la meilleure approximation possible du mathématique (x + y) / 2.la source
Pour les formats binaires à virgule flottante IEEE-754, illustrés par le
binary64
calcul (double précision), S. Boldo a formellement prouvé que l'algorithme simple présenté ci-dessous fournit la moyenne correctement arrondie.Sylvie Boldo, "Vérification formelle des programmes calculant la moyenne à virgule flottante." Dans International Conference on Formal Engineering Methods , pp. 17-32. Springer, Cham, 2015. ( projet en ligne )
binary64
Cela donne l'exemple de
ISO-C99
code suivant:Dans des travaux de suivi récents, S. Boldo et ses co-auteurs ont montré comment obtenir les meilleurs résultats possibles pour les formats décimaux à virgule flottante IEEE-754 en utilisant des opérations de multiplication-addition (FMA) fusionnées et une précision bien connue. bloc de construction doublant (TwoSum):
Sylvie Boldo, Florian Faissole et Vincent Tourneur, «Un algorithme formellement prouvé pour calculer la moyenne correcte des nombres décimaux à virgule flottante». In 25th IEEE Symposium on Computer Arithmetic (ARITH 25) , juin 2018, p. 69-75. ( projet en ligne )
la source
Bien qu'il ne soit pas très efficace en termes de performances, il existe un moyen très simple de (1) s'assurer qu'aucun des nombres n'est supérieur à l'un
x
ou à l' autrey
(pas de débordements) et (2) garder le point flottant aussi "précis" que possible (et (3) , comme bonus supplémentaire, même si la soustraction est utilisée, aucune valeur ne sera jamais stockée sous forme de nombres négatifs.En fait, si vous voulez vraiment rechercher la précision, vous n'avez même pas besoin d'effectuer la division sur place; il suffit de renvoyer les valeurs de
min(x, y)
etdifference
que vous pouvez utiliser pour simplifier logiquement ou manipuler plus tard.la source
2,4,9
, ce n'est pas la même chose que la moyenne de3,9
.x
ety
sont à virgule flottante, votre calcul produit un virgule flottante le plus proche de(x+y)/2
?Convertissez en précision supérieure, ajoutez-y les valeurs et reconvertissez.
Il ne doit pas y avoir de débordement dans la précision supérieure et si les deux sont dans la plage de virgule flottante valide, le nombre calculé doit également être à l'intérieur.
Et cela devrait être entre les deux, le pire des cas seulement la moitié du plus grand nombre si la précision n'est pas suffisante.
la source
Théoriquement,
x/2
peut être calculé en soustrayant 1 de la mantisse.Cependant, l'implémentation d'opérations au niveau du bit comme celle-ci n'est pas nécessairement simple, surtout si vous ne connaissez pas le format de vos nombres à virgule flottante.
Si vous pouvez le faire, toute l'opération est réduite à 3 additions / soustractions, ce qui devrait être une amélioration significative.
la source
Je pensais dans le même sens que @Roland Heath mais je ne peux pas encore commenter, voici mon point de vue:
x/2
peut être calculé en soustrayant 1 de l' exposant (pas la mantisse, la soustraction de 1 de la mantisse soustrait2^(value_of_exponent-length_of_mantissa)
de la valeur globale).Sans restriction du cas général, supposons
x < y
. (Six > y
, réétiquetez les variables. Six = y
,(x+y) / 2
est trivial.)(x+y) / 2
enx/2 + y/2
, qui peut être effectué par deux soustractions entières (par une de l'exposant)x
rendrax/2
plus petit que représentable (en supposant que la mantisse est représentée avec un interligne implicite 1).x
, déplacezx
la mantisse de un vers la droite (et ajoutez le premier implicite, le cas échéant).x
vers la droite en fonction de l'exposant dey
.x
n'ait été complètement déplacée. Si les deux exposants étaient minimes, les principaux déborderont, ce qui est correct, car ce débordement est censé redevenir implicite.la source