Pourquoi certains nombres perdent-ils en précision lorsqu'ils sont stockés sous forme de nombres à virgule flottante?
Par exemple, le nombre décimal 9.2
peut être exprimé exactement comme un rapport de deux entiers décimaux (92/10
), les deux pouvant être exprimés exactement en binaire (0b1011100/0b1010
). Toutefois, le même rapport stocké sous forme de nombre à virgule flottante n’est jamais exactement égal à 9.2
:
32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875
Comment un nombre aussi simple en apparence peut-il être "trop grand" pour être exprimé en 64 bits de mémoire?
Dans la plupart des langages de programmation, les nombres à virgule flottante sont représentés beaucoup comme la notation scientifique : avec un exposant et une mantisse (également appelée le significande). Un nombre très simple, disons 9.2
, est en réalité cette fraction:
5179139571476070 * 2 -49
Où l'exposant est -49
et la mantisse est 5179139571476070
. La raison pour laquelle il est impossible de représenter some nombres décimaux de cette façon est que l'exposant et la mantisse doivent être des entiers. En d'autres termes, tous les flottants doivent être un entier == multiplié par un puissance entière de 2 .
9.2
peut être simplement 92/10
, mais 10 ne peut pas être exprimé sous la forme 2n if n est limité à des valeurs entières.
Premièrement, quelques fonctions pour voir les composants qui constituent un float
de 32 et 64 bits. Glissez dessus si vous ne vous souciez que de la sortie (exemple en Python):
def float_to_bin_parts(number, bits=64):
if bits == 32: # single precision
int_pack = 'I'
float_pack = 'f'
exponent_bits = 8
mantissa_bits = 23
exponent_bias = 127
Elif bits == 64: # double precision. all python floats are this
int_pack = 'Q'
float_pack = 'd'
exponent_bits = 11
mantissa_bits = 52
exponent_bias = 1023
else:
raise ValueError, 'bits argument must be 32 or 64'
bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]
Il y a beaucoup de complexité derrière cette fonction, et ce serait assez complexe à expliquer, mais si cela vous intéresse, la ressource importante pour nos besoins est le module struct .
Python float
est un nombre double précision de 64 bits. Dans d'autres langages tels que C, C++, Java et C #, la double précision a un type distinct double
, qui est souvent implémenté en tant que 64 bits.
Lorsque nous appelons cette fonction avec notre exemple, 9.2
, voici ce que nous obtenons:
>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']
Vous verrez que j'ai divisé la valeur de retour en trois composants. Ces composants sont:
Le signe est stocké dans le premier composant en tant que bit unique. C'est facile à expliquer: 0
signifie que le flottant est un nombre positif; 1
signifie que c'est négatif. Puisque 9.2
est positif, notre signe est 0
.
L'exposant est stocké dans la composante centrale sous forme de 11 bits. Dans notre cas, 0b10000000010
. En décimal, cela représente la valeur 1026
. Une bizarrerie de ce composant est que vous devez soustraire un nombre égal à 2(nombre de bits) - 1 - 1 pour obtenir le véritable exposant; dans notre cas, cela signifie soustraire 0b1111111111
(nombre décimal 1023
) pour obtenir le véritable exposant, 0b00000000011
(nombre décimal 3).
La mantisse est stockée dans le troisième composant en tant que 52 bits. Cependant, il y a aussi une bizarrerie à ce composant. Pour comprendre cette bizarrerie, considérons un nombre en notation scientifique, comme ceci:
6.0221413x1023
La mantisse serait le 6.0221413
. Rappelez-vous que la mantisse en notation scientifique commence toujours par un seul chiffre différent de zéro. La même chose vaut pour le binaire, sauf que le binaire n'a que deux chiffres: 0
et 1
. Ainsi, la mantisse binaire toujours commence par 1
! Lorsqu'un float est stocké, le 1
situé à l'avant de la mantisse binaire est omis pour économiser de l'espace; nous devons le replacer devant notre troisième élément pour obtenir le true mantissa:
1.00100110011001100110011001100110011001100110011001100110
Cela implique plus qu’un simple ajout, car les bits stockés dans notre troisième composant représentent en réalité la partie fractionnaire de la mantisse, à droite du point de radix . .
Lorsque nous traitons des nombres décimaux, nous "déplaçons le point décimal" en multipliant ou en divisant par des puissances de 10. En binaire, nous pouvons faire la même chose en multipliant ou en divisant par des puissances de 2. Comme notre troisième élément a 52 bits, nous divisons il par 252 pour le déplacer 52 places à droite:
0.001001100110011001100110011001100110011001100110011001100110
En notation décimale, cela revient à diviser 675539944105574
par 4503599627370496
pour obtenir 0.1499999999999999
. (Ceci est un exemple de ratio qui peut être exprimé exactement en binaire, mais seulement approximativement en décimal; pour plus de détails, voir: 675539944105574/4503599627370496 .)
Maintenant que nous avons transformé le troisième composant en un nombre fractionnaire, ajouter 1
donne la vraie mantisse.
0
pour positif, 1
pour négatif1
pour obtenir la vraie mantisseEn réunissant les trois parties, on nous donne ce nombre binaire:
1.001001100110011001100110011001100110011001100110011001100110 x 1011
Ce que nous pouvons ensuite convertir de binaire en décimal:
1.1499999999999999 x 23 (inexact!)
Et multipliez-vous pour afficher la représentation finale du nombre que nous avons commencé avec (9.2
) après avoir été stockée sous forme de valeur à virgule flottante:
9.1999999999999993
Maintenant que nous avons construit le nombre, il est possible de le reconstruire en une fraction simple:
1.001001100110011001100110011001100110011001100110011001100110 x 1011
Décaler la mantisse en un nombre entier:
100100110011001100110011001100110011001100110011001100110 x 1011-110100
Convertir en décimal:
5179139571476070 x 23-52
Soustrayez l'exposant:
5179139571476070 x 2-49
Transforme l’exposant négatif en division:
5179139571476070/249
Multiplier exposant:
5179139571476070/562949953421312
Qui est égal à:
9.1999999999999993
>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']
Déjà, vous pouvez voir que la mantisse n’est que de 4 chiffres suivis de beaucoup de zéros. Mais passons à travers les pas.
Assemblez la notation scientifique binaire:
1,0011 x 1011
Décalez le point décimal:
10011 x 1011-100
Soustrayez l'exposant:
10011 x 10-1
Binaire en décimal:
19 x 2-1
Exposant négatif à la division:
19/21
Multiplier exposant:
19/2
Équivaut à:
9,5
Ce n'est pas une réponse complète ( mhlester j'ai déjà couvert beaucoup de bonnes choses que je ne vais pas dupliquer), mais je voudrais souligner combien la représentation d'un nombre dépend de la base sur laquelle vous travaillez.
Dans good-ol 'base 10, nous l’écrivons comme suit:
Lorsque nous examinons ces représentations, nous avons tendance à associer chacune d’elles à la fraction 2/3, même si seule la première représentation est mathématiquement égale à la fraction. Les deuxième et troisième représentations/approximations ont une erreur de l'ordre de 0,001, ce qui est en réalité bien pire que l'erreur entre 9.2 et 9.1999999999999993. En fait, la deuxième représentation n'est même pas arrondie correctement! Néanmoins, nous n'avons pas de problème avec 0.666 comme approximation du nombre 2/3, donc nous ne devrions pas vraiment avoir de problème avec la façon dont 9.2 est approximée dans la plupart des programmes. ( Oui, dans certains programmes, cela compte.)
Alors, voici où les bases de chiffres sont cruciales. Si nous essayions de représenter 2/3 en base 3, alors
(2/3)dix = 0,23
En d’autres termes, nous avons une représentation exacte et finie du même nombre en changeant de base! Le problème est que même si vous pouvez convertir n'importe quel nombre en n'importe quelle base, tous les nombres rationnels ont des représentations finies exactes dans certaines bases mais pas dans d'autres.
Pour ramener ce point à la maison, regardons 1/2. Vous pourriez être surpris que même si ce nombre parfaitement simple a une représentation exacte en base 10 et 2, il nécessite une représentation répétée en base 3.
(1/2)dix = 0.5dix = 0,12 = 0.1111 ...3
Parce que souvent, ils sont des approximations de rationnels qui ne peuvent pas être représentés finement en base 2 (les chiffres sont répétés), et en général, ils sont des approximations de nombres réels (éventuellement irrationnels) qui peuvent ne pas être représentables par des nombres finis dans any base.
Bien que toutes les autres réponses soient bonnes, il ne manque qu'une chose:
Il est impossible de représenter des nombres irrationnels (par exemple, π, sqrt(2)
, log(3)
, etc.) avec précision!
Et c’est pourquoi on les appelle irrationnelles. Aucune quantité de stockage de bits dans le monde ne suffirait pour en contenir un seul. Seul l'arithmétique symbolique est capable de préserver leur précision.
Toutefois, si vous limitez vos besoins en calcul aux nombres rationnels, seul le problème de la précision devient gérable. Vous auriez besoin de stocker une paire d'entiers (éventuellement très grands) a
et b
pour contenir le nombre représenté par la fraction a/b
. Toute votre arithmétique devrait être faite sur des fractions comme dans les mathématiques au lycée (par exemple a/b * c/d = ac/bd
).
Mais bien sûr, vous rencontreriez le même type de problème lorsque pi
, sqrt
, log
, sin
, etc. sont impliqués.
TL; DR
Pour l'arithmétique accélérée matériellement, seule une quantité limitée de nombres rationnels peut être représentée. Chaque nombre non représentable est approximé. Certains chiffres (irrationnels) ne peuvent jamais être représentés, peu importe le système.
Il y a une infinité de nombres réels (tellement que vous ne pouvez pas les énumérer), et il y a une infinité de nombres rationnels (il est possible de les énumérer).
La représentation en virgule flottante est une représentation finie (comme n'importe quoi dans un ordinateur), si bien qu’un grand nombre de nombres sont inévitablement impossibles à représenter. En particulier, 64 bits ne permettent de distinguer que 18 446 744 073 709 551 616 valeurs différentes (ce qui n’est rien comparé à l’infini). Avec la convention standard, 9.2 n'en fait pas partie. Celles qui le peuvent ont la forme m.2 ^ e pour certains entiers m et e.
Vous pouvez créer un système de numération différent, basé par exemple sur 10, où 9.2 aurait une représentation exacte. Mais d'autres chiffres, par exemple 1/3, seraient toujours impossibles à représenter.
Notez également que les nombres à virgule flottante en double précision sont extrêmement précis. Ils peuvent représenter n’importe quel nombre dans une très large gamme avec jusqu’à 15 chiffres exacts. Pour les calculs quotidiens, 4 ou 5 chiffres suffisent amplement. Vous n'aurez jamais vraiment besoin de ces 15 ans, à moins de vouloir compter chaque milliseconde de votre vie.
Pourquoi ne pouvons-nous pas représenter 9.2 en virgule flottante binaire?
Les nombres à virgule flottante sont (simplifiant légèrement) un système de numérotation de position avec un nombre restreint de chiffres et un point de base mobile.
Une fraction ne peut être exprimée exactement à l'aide d'un nombre fini de chiffres dans un système de numérotation par position que si les facteurs premiers du dénominateur (lorsque la fraction est exprimée dans ses termes les plus bas) sont des facteurs de la base.
Les facteurs premiers de 10 sont 5 et 2. Ainsi, en base 10, nous pouvons représenter n’importe quelle fraction de la forme a/(2b5c).
Par contre, le seul facteur premier de 2 est 2, de sorte qu'en base 2, nous ne pouvons représenter que des fractions de la forme a/(2b)
Pourquoi les ordinateurs utilisent-ils cette représentation?
Parce que c'est un format simple à utiliser et qu'il est suffisamment précis pour la plupart des cas. Fondamentalement, les scientifiques utilisent la "notation scientifique" pour la même raison et arrondissent leurs résultats à un nombre raisonnable de chiffres à chaque étape.
Il serait certainement possible de définir un format de fraction, avec (par exemple) un numérateur à 32 bits et un dénominateur à 32 bits. Il serait en mesure de représenter des nombres que la virgule flottante à double précision IEEE ne pourrait pas, mais il y aurait également de nombreux nombres pouvant être représentés sous forme de virgule flottante à double précision qui ne pourraient pas être représentés dans un tel format de fraction de taille fixe.
Cependant, le gros problème est qu’un tel format est difficile à faire des calculs. Pour deux raisons.
Certaines langues offrent des types de fractions, mais elles le font généralement en combinaison avec une précision arbitraire, ce qui évite d'avoir à s'inquiéter de l'approximation des fractions, mais cela crée son propre problème, lorsqu'un nombre passe par un grand nombre d'étapes de calcul, la taille du dénominateur et par conséquent, le stockage nécessaire pour la fraction peut exploser.
Certaines langues offrent également des types à virgule flottante décimale. Celles-ci sont principalement utilisées dans des scénarios où il est important que les résultats obtenus par l’ordinateur correspondent aux règles d’arrondi préexistantes écrites en pensant à l’homme (calculs financiers principalement). Celles-ci sont légèrement plus difficiles à utiliser que les virgules flottantes binaires, mais le problème majeur est que la plupart des ordinateurs ne les prennent pas en charge.