Fraction Python

Le but de cette histoire

Histoire à ajouter 10 fois

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.

Comment représenter les nombres en binaire

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.

Représentation des nombres à virgule flottante

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.

une addition

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.

Représentation fractionnaire Python

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;

Résumé

Recommended Posts

Fraction Python
Python
python kafka
Les bases de Python ⑤
Résumé Python
Python intégré
Notation d'inclusion Python
Technique Python
Étudier Python
Compte à rebours Python 2.7
Mémorandum Python
Python FlowFishMaster
Service Python
astuces python
fonction python ①
Les bases de Python
Mémo Python
ufo-> python (3)
Notation d'inclusion Python
Installer python
Python Singleton
Les bases de Python ④
Mémorandum Python 2
mémo python
Python Jinja2
Incrément Python
atCoder 173 Python
[Python] fonction
Installation de Python
Essayez Python
Mémo Python
Itératif Python
Python2 + mot2vec
Fonctions Python
Tutoriel Python
underbar python C'est ce que
Résumé Python
Démarrer python
[Python] Trier
Remarque: Python
Les bases de Python ③
Sortie du journal python
Les bases de Python
[Scraping] Scraping Python
Mise à jour Python (2.6-> 2.7)
mémo python
Mémorandum Python
Python #sort
ufo-> python
Python nslookup
apprentissage de python
[Rpmbuild] Python 3.7.3.
Python au prorata (1)
mémorandum python
Télécharger Python
mémorandum python
Mémo Python
a commencé python