Pour l'arithmétique IEEE-754, y a-t-il une garantie de 0 ou 1 unité à la dernière place pour la précision des réciproques? À partir de là, y a-t-il une garantie d'erreur sur l'inverse d'un réciproque?Est-ce qu'un réciproque à virgule flottante toujours aller-retour?
Répondre
[Tout ci-dessous suppose un format binaire IEEE 754 fixe, avec une forme de ronde à la plus proche que l'arrondissement de mode.]
Depuis réciproque (calculé comme 1/x
) est une opération arithmétique de base, 1
est exactement représentable, et les opérations arithmétiques sont garantis correctement arrondis par le standard, le résultat réciproque est garanti à 0.5
unités à la dernière place de la vraie valeur. (Ceci est valable pour tout des opérations arithmétiques de base spécifiées dans la norme.)
L'inverse de la réciproque d'une valeur x
ne sont pas garanties pour être égale à x
, en général. Un exemple rapide avec le format IEEE 754 binary64:
>>> x = 1.8
>>> 1.0/(1.0/x)
1.7999999999999998
>>> x == 1.0/(1.0/x)
False
Cependant, en supposant que le débordement et le sousécoulement sont évités, et que x
est fini, non nulle, et sont vraies exactement représentable, les résultats suivants:
La valeur de
1.0/(1.0/x)
sera différente dex
de pas plus de 1 unité à la dernière place.Si le significand de
x
(normalisé pour se situer dans la plage[1.0, 2.0)
comme d'habitude) est plus petit quesqrt(2)
, l'réciproque-t aller-retour:1.0/(1.0/x) == x
.
Esquisse de la preuve: sans perte de généralité, on peut supposer que x
est positif, et à l'échelle x
par une puissance de deux pour qu'il se trouve dans la gamme [1.0, 2.0)
. Les résultats ci-dessus sont manifestement vrais dans le cas où x
est une puissance exacte de deux, alors supposons que ce n'est pas le cas (cela sera utile dans la deuxième étape ci-dessous). La preuve ci-dessous est donnée pour la précision spécifique au format IEEE 754 binary64, mais elle s'adapte directement à tout format binaire IEEE 754.
Ecrire 1/x
pour la vraie valeur de l'inverse, avant l'arrondissement, et laisser y
être le (unique, il se trouve) le plus proche flotteur représentable binary64 à 1/x
. Puis:
depuis
y
est le plus proche flotteur1/x
, et les deuxy
et1/x
sont dans le binade[0.5, 1.0]
, où l'espacement entre les flotteurs successifs est exactement2^-53
, nous avons|y - 1/x| <= 2^-54
. En fait, nous pouvons faire un peu mieux:nous avons en fait une inégalité stricte ci-dessus:
|y - 1/x| < 2^-54
. Si|y - 1/x|
étaient exactement égal à2^-54
, alors1/x
serait exactement représentable en point flottant binaire de précision arbitraire (puisque les deuxy
et2^-54
sont).Mais les seuls flottants binairesx
pour lesquels1/x
est exactement représentable à une certaine précision sont des puissances de deux, et nous avons déjà exclu ce cas.si
x < sqrt(2)
puis1/x > x/2
, d'où (arrondi à la fois au flotteur représentable le plus proche), nous avonsy >= x/2
, doncx/y <= 2
.maintenant
x - 1/y = (y - 1/x) x/y
et des limites sur|y - 1/x|
etx/y
(en supposant encore quex < sqrt(2)
) nous obtenons|x - 1/y| < 2^-53
. Il s'ensuit quex
est le flottant représentable le plus proche de1/y
,1/y
arrondit àx
et l'aller-retour réussit. Cela complète la preuve de la partie 2.dans le cas général
x < 2
, nous avonsx/y < 4
, donc|x - 1/y| < 2^-52
. Cela fait1/y
au plus 1 ULP loin dex
, qui complète la preuve d'une partie 1.
Voici une démonstration du seuil sqrt(2)
: en utilisant Python, nous prenons un million de flotteurs au hasard dans la gamme [1.0, 2.0)
, et identifier ceux qui ne font pas le tour de la réciproque. Tous les échantillons plus petits que sqrt(2)
passent le roundtrip.
>>> import random
>>> samples = [random.uniform(1.0, 2.0) for _ in range(10**6)]
>>> bad = [x for x in samples if 1.0/(1.0/x) != x]
>>> len(bad)
171279
>>> min(bad)
1.4150519879892107
>>> import math
>>> math.sqrt(2)
1.4142135623730951
et une démonstration que l'erreur maximale est de pas plus de 1 ULP, en général (pour le format de binary64, dans le binade [1.0, 2.0), une unité à la dernière place est 2^-52) :
>>> samples = [random.uniform(1.0, 2.0) for _ in range(10**6)]
>>> max(abs(x - 1.0/(1.0/x)) for x in samples)
2.220446049250313e-16
>>> 2**-52
2.220446049250313e-16
Voici un exemple avec le format IEEE 754 binary64 montre que la restriction à éviter soupassement est nécessaire:
>>> x = 1.3e308
>>> x_roundtrip = 1.0/(1.0/x)
>>> x.hex()
'0x1.72409614c1e6ap+1023'
>>> x_roundtrip.hex()
'0x1.72409614c1e6cp+1023'
Voici le résultat x_roundtrip
diffère du b d'origine y deux unités à la dernière place, car 1/x
était plus petit que le plus petit flottant représentable normal, et n'était donc pas représenté avec la même précision que x
. Note finale: étant donné que la norme IEEE 754-2008 couvre également les types à virgule flottante décimale, je dois mentionner que la preuve ci-dessus est presque textuelle dans le cas décimal, établissant que pour les flottants de moins significatifs que sqrt(10)
, alors que pour les flottants décimaux généraux (évitant à nouveau le débordement et le débordement), nous ne sommes jamais éloignés de plus d'une unité à la dernière place. Cependant, il y a une certaine finesse théorique nécessaire pour montrer que l'inégalité clé |x - 1/y| < 1/2 10^(1-p)
est toujours stricte: on finit par devoir montrer que la quantité 1 + 16 10^(2p-1)
n'est jamais un nombre carré (ce qui est vrai, mais c'est sans doute hors de la portée de ce site inclure la preuve ici).
>>> from decimal import Decimal, getcontext
>>> import random
>>> getcontext().prec = 6
>>> samples = [+Decimal(random.uniform(1.0, 10.0)) for _ in range(10**6)]
>>> bad = [x for x in samples if 1/(1/x) != x]
>>> min(bad)
Decimal('3.16782')
Hmmm ... Je devais chercher "scope". –
Cette question est vaguement liée à la question de la mise en œuvre multiplication simple précision par une constante par une multiplication en double précision par l'inverse suivie par arrondi à simple précision, également répondu par Mark: https: // stackoverflow .com/questions/19589973/implementation-single-precision-division-as-double-precision-multiplication –