Ajouter 1 10 fois donne plus de 10 (ou égal à 10).
int_sum.py
x = 0
for i in range(10):
x += 1
print(x < 10)
print(x == 10)
print(type(x))
C'est naturel, non?
% python int_sum.py
False
True
<type 'int'>
Et si vous ajoutez 0,1 10 fois?
float_sum.py
x = 0.0
for i in range(10):
x += 0.1
print(x < 1)
print(type(x))
Pas plus de 1 ...
% python float_sum.py
True
<type 'float'>
Les valeurs numériques sont représentées en binaire sur les ordinateurs, mais cela se produit parce que les fractions sont représentées en nombres binaires à chiffres finis.
Explication détaillée http://docs.python.jp/3/tutorial/floatingpoint.html Cependant, regardons comment exprimer des «nombres à virgule flottante», qui est la base de l'explication.
Avant d'entrer dans le sujet principal, examinons comment représenter des nombres en binaire.
Pour représenter les nombres en décimal, utilisez des nombres de 0 à 9 et des puissances de 10, par exemple.
2015
= 2*1000 + 0*100 + 1*10 + 5*1
= 2*10^3 + 0*10^2 + 1*10^1 + 5*10^0
J'ai écrit comme ça.
De même, en nombres binaires, les nombres de 0 à 1 et la puissance de 2
11001011
= 1*2^7 + 1*2^6 + 0*2^5 + 0*2^4 + 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0
Représente un nombre comme. De plus, étant donné que le nombre de chiffres en notation binaire est grand et qu'il est difficile à écrire, lors de l'explication, il est souvent écrit sous une forme compacte en utilisant des nombres hexadécimaux. Par exemple, le nombre binaire 11001011 est écrit comme 0xcb dans le nombre hexadécimal.
Analysons à quoi ressemble réellement la représentation fractionnaire en utilisant un simple programme en langage C.
float.c
#include <stdio.h>
int main(int argc, char *argv[])
{
double z;
if (argc != 2) {
fprintf(stderr, "usage: %s str\n", argv[0]);
return -1;
}
if (sscanf(argv[1], "%lf", &z) != 1) {
fprintf(stderr, "parse failed\n");
return -1;
}
printf("double: %f\n", z);
printf("hex: %016llx\n", *(unsigned long long *)&z);
return 0;
}
Dans ce programme
Je fais ça.
% gcc -o float float.c
Si vous compilez avec, donnez un argument et exécutez-le, ce sera comme suit.
% ./float 1
double: 1.000000
hex: 3ff0000000000000
% ./float -1
double: -1.000000
hex: bff0000000000000
% ./float 0.5
double: 0.500000
hex: 3fe0000000000000
% ./float -0.5
double: -0.500000
hex: bfe0000000000000
% ./float 2
double: 2.000000
hex: 4000000000000000
En comparant la notation hexadécimale de 1 et -1, 0,5 et -0,5, il semble que la différence de signe correspond à la différence entre 3 et b au début. Lorsqu'elle est exprimée en nombres binaires
Vous pouvez donc vous attendre à un nombre positif si le bit le plus significatif est 0 et un nombre négatif s'il est 1. Si c'est correct
Donc -2 devrait être c000000000000000. Essayons-le réellement.
% ./float -2
double: -2.000000
hex: c000000000000000
Il semble que l'attente était correcte. Ensuite, si nous nous concentrons sur la partie excluant le bit de code
Lorsqu'ils sont doublés, il semble que les 3 premiers caractères (valeur de 11 bits à l'exclusion du bit de signe) sont incrémentés de 1. Par conséquent, on peut s'attendre à ce que 4 (2 ^ 2) soit 4010000000000000, mais lorsque vous le vérifiez réellement,
% ./float 4
double: 4.000000
hex: 4010000000000000
C'est devenu. En d'autres termes, il a été constaté que les 11 bits suivant le bit de code représentent l'exposant (le nombre multiplié par 2).
Alors, que représentent les 13 caractères restants (52 bits)? Étant donné le nombre binaire 1,1, soit 1 + 1/2 = 1,5
% ./float 1.5
double: 1.500000
hex: 3ff8000000000000
0x8 (1000) apparaît en haut. Binaire 1,11 C'est-à-dire 1 + 1/2 + 1/4 = 1,75
% ./float 1.75
double: 1.750000
hex: 3ffc000000000000
Dans ce cas, il commence à 0xc (1100). Puis le nombre binaire 1.111, c'est-à-dire 1 + 1/2 + 1/4 + 1/8 = 1.875
% ./float 1.875
double: 1.875000
hex: 3ffe000000000000
Et si vous résumez ici
En comparant la gauche et la droite, il semble que la valeur de gauche soit la valeur de droite, le 1 au-dessus du point décimal étant omis. Alors, que se passe-t-il avec 1.00001? Si vous donnez 1 + 1/32 = 1.03125 comme argument
% ./float 1.03125
double: 1.031250
hex: 3ff0800000000000
Il est devenu 0x08 (0000 1000).
Encore une fois, cela peut être expliqué par l'omission du 1 au-dessus de la virgule décimale. Dans le cas de la puissance de 2 qui a été vue au début, les 52 bits inférieurs étaient 0, mais si la puissance de 2 est exprimée en binaire, le plus significatif est 1 et le reste est 0, ce qui est également expliqué. Est attaché.
D'après ce que nous avons observé jusqu'à présent, l'expression des fractions peut être exprimée comme une expression.
(-1)^(x >> 63) * 2^((0x7ff & (x >> 52)) - 0x3ff) * ((x & 0x000fffffffffffff) * 2^(-52) + 1)
Ce sera. Notez que 0 ne peut pas être exprimé sous cette forme, mais il joue un rôle important lors de la multiplication, donc
% ./float 0
double: 0.000000
hex: 0000000000000000
Et tous les bits sont mis à 0.
Résumé
La fraction a été exprimée sous la forme de.
Imprimons la valeur au milieu par le calcul de l'addition de 0,1 10 fois, ce que nous avons vu au début.
float_sum.c
#include <stdio.h>
int main()
{
double z = 0.0;
for (int i = 0; i < 10; i++)
{
z += 0.1;
printf("hex: %016llx\n", *(unsigned long long *)&z);
}
return 0;
}
% gcc -o float_sum float_sum.c
% ./float_sum
hex: 3fb999999999999a
hex: 3fc999999999999a
hex: 3fd3333333333334
hex: 3fd999999999999a
hex: 3fe0000000000000
hex: 3fe3333333333333
hex: 3fe6666666666666
hex: 3fe9999999999999
hex: 3feccccccccccccc
hex: 3fefffffffffffff
Tout d'abord, la première ligne doit représenter le nombre décimal 0,1,
2^((0x3fb - 0x3ff) * (0x1999999999999a * 2^(-52))
= 0x1999999999999a * 2^(-56)
= 7205759403792794.0 / 72057594037927936.0
Il n'est pas possible de représenter avec précision 0,1 (il est un peu plus grand que 0,1). Lorsque le nombre décimal 0,1 est exprimé en binaire, il devient une fraction circulaire, et en hexadécimal, 9 continue indéfiniment, mais cela montre qu'il y a une erreur en l'exprimant en un nombre fini de chiffres.
Ensuite, voyons comment calculer l'addition. Les nombres à virgule flottante étaient représentés par des exposants et des nombres incorrects, mais leur addition est en décimal.
10 * 3 + 100 * 5 = 100 * (0.3 + 5) = 100 * 5.3
Faites quelque chose de similaire à. En fait, si vous écrivez une fraction sous la forme (exposant, incorrect),
3fb999999999999a + 3fb999999999999a
= (3fb, 0x1999999999999a) + (3fb, 0x1999999999999a)
= (3fb, 0x1999999999999a + 0x1999999999999a)
= (3fb, 0x33333333333334)
= (3fc, 0x1999999999999a)
= 3fc999999999999a
3fc999999999999a + 3fb999999999999a
= (3fc, 0x1999999999999a) + (3fb, 0x1999999999999a)
= (3fc, 0x1999999999999a) + (3fc, 0xccccccccccccd)
= (3fc, 0x1999999999999a + 0xccccccccccccd)
= (3fc, 0x26666666666667)
= (3fd, 0x13333333333334)
= 3fd3333333333334
Voici la règle d'arrondi lors du décalage de nombres incorrects
j'utilise
0x33333333333334 = ...00110100
Décalage 1 bit →...1010 = 0x1999999999999a
0x999999999999a = ...10011010
Décalage 1 bit →...1101 = 0xccccccccccccd
0x26666666666667 = ...01100111
Décalage 1 bit →...0100 = 0x13333333333334
Encore une fois, nous devons arrondir pour représenter l'exposant avec 53 bits, et nous pouvons voir qu'il y a plus d'erreur. En raison de ces erreurs, ajouter 0,1 10 fois entraînera moins de 1.
Maintenant que je comprends la représentation des nombres à virgule flottante à l'aide d'un programme en langage C Enfin, assurons-nous que Python utilise réellement la même expression.
float_sum_hex.py
x = 0.0
for i in range(10):
x += 0.1
print(x.hex())
% python float_sum_hex.py
0x1.999999999999ap-4
0x1.999999999999ap-3
0x1.3333333333334p-2
0x1.999999999999ap-2
0x1.0000000000000p-1
0x1.3333333333333p-1
0x1.6666666666666p-1
0x1.9999999999999p-1
0x1.cccccccccccccp-1
0x1.fffffffffffffp-1
La méthode d'écriture est légèrement différente, mais les mêmes valeurs que celles vues en langage C apparaissent.
En fait, si vous regardez l'implémentation CPython, la valeur flottante Python est représentée par un double C.
cpython/Include/floatobject.h
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
Recommended Posts