Python-Fraktion

Das Ziel dieser Geschichte

Story 10 mal hinzufügen

1 10-maliges Hinzufügen ergibt mehr als 10 (oder gleich 10).

int_sum.py


x = 0
for i in range(10):
    x += 1
print(x < 10)
print(x == 10)
print(type(x))

Es ist natürlich, nicht wahr?

% python int_sum.py
False
True
<type 'int'>

Was ist, wenn Sie 0,1 10-mal hinzufügen?

float_sum.py


x = 0.0
for i in range(10):
    x += 0.1
print(x < 1)
print(type(x))

Nicht mehr als 1 ...

% python float_sum.py
True
<type 'float'>

Auf Computern werden numerische Werte binär dargestellt. Dies liegt jedoch daran, dass Brüche in binären Zahlen mit endlichen Ziffern dargestellt werden.

Ausführliche Erklärung http://docs.python.jp/3/tutorial/floatingpoint.html Schauen wir uns jedoch an, wie "Gleitkommazahlen" ausgedrückt werden, die die Grundlage der Erklärung bilden.

Wie man Zahlen in Binärform darstellt

Bevor wir uns dem Hauptthema zuwenden, wollen wir uns ansehen, wie Zahlen in Binärform dargestellt werden.

Verwenden Sie zum Darstellen von Zahlen in Dezimalzahlen beispielsweise Zahlen von 0 bis 9 und Potenzen von 10.

2015
= 2*1000 + 0*100  + 1*10   + 5*1
= 2*10^3 + 0*10^2 + 1*10^1 + 5*10^0

Ich habe so geschrieben.

Ebenso in Binärzahlen die Zahlen 0 bis 1 und die Potenz von 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äsentiert eine Zahl wie. Da die Anzahl der Ziffern in binärer Notation groß ist und es schwierig ist zu schreiben, wird sie bei der Erklärung häufig in kompakter Form unter Verwendung von Hexadezimalzahlen geschrieben. Beispielsweise wird die Binärzahl 11001011 als 0xcb in die Hexadezimalzahl geschrieben.

Darstellung von Gleitkommazahlen

Lassen Sie uns mit einem einfachen Programm in C-Sprache analysieren, wie die gebrochene Darstellung tatsächlich aussieht.

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;
}

In diesem Programm

Ich mache das

% gcc -o float float.c

Wenn Sie mit kompilieren, ein Argument angeben und es ausführen, sieht es wie folgt aus.

% ./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

Vergleicht man die hexadezimale Notation von 1 und -1, 0,5 und -0,5, so scheint der Vorzeichenunterschied dem Unterschied zwischen 3 und b am Anfang zu entsprechen. In Binärzahlen ausgedrückt

Sie können also eine positive Zahl erwarten, wenn das höchstwertige Bit 0 ist, und eine negative Zahl, wenn es 1 ist. Wenn es richtig ist

-2 sollte also c000000000000000 sein. Lass es uns tatsächlich versuchen.

% ./float -2
double: -2.000000
hex: c000000000000000

Es scheint, dass die Erwartung richtig war. Als nächstes konzentrieren wir uns auf den Teil ohne das Codebit

Und wenn es verdoppelt wird, scheinen die ersten 3 Zeichen (11-Bit-Wert ohne das Vorzeichenbit) um 1 erhöht zu werden. Daher kann erwartet werden, dass 4 (2 ^ 2) 4010000000000000 ist, aber wenn Sie es tatsächlich überprüfen,

% ./float 4
double: 4.000000
hex: 4010000000000000

Es wurde. Mit anderen Worten wurde gefunden, dass die 11 Bits nach dem Codebit den Exponenten darstellen (die Zahl multipliziert mit 2).

Was bedeuten die verbleibenden 13 Zeichen (52 Bit)? Bei der Binärzahl 1.1 ist 1 + 1/2 = 1,5

% ./float 1.5
double: 1.500000
hex: 3ff8000000000000

Oben wird 0x8 (1000) angezeigt. Binär 1.11 Das heißt, 1 + 1/2 + 1/4 = 1,75

% ./float 1.75
double: 1.750000
hex: 3ffc000000000000

In diesem Fall beginnt es bei 0xc (1100). Dann ist die Binärzahl 1.111, dh 1 + 1/2 + 1/4 + 1/8 = 1,875

% ./float 1.875
double: 1.875000
hex: 3ffe000000000000

Und wenn Sie bis hierher zusammenfassen

Beim Vergleich von links und rechts scheint der Wert links der Wert rechts zu sein, wobei die 1 über dem Dezimalpunkt weggelassen wird. Was passiert also mit 1.00001? Wenn Sie 1 + 1/32 = 1.03125 als Argument angeben

% ./float 1.03125
double: 1.031250
hex: 3ff0800000000000

Es wurde 0x08 (0000 1000).

Dies kann wiederum durch das Weglassen der 1 über dem Dezimalpunkt erklärt werden. Im Fall der Potenz von 2, die zu Beginn gesehen wurde, waren die unteren 52 Bits 0, aber wenn die Potenz von 2 in Binär ausgedrückt wird, ist die höchstwertige 1 und der Rest ist 0, was ebenfalls erklärt wird. Es ist angehängt.

Nach dem, was wir bisher beobachtet haben, kann der Ausdruck von Brüchen als Ausdruck ausgedrückt werden.

(-1)^(x >> 63) * 2^((0x7ff & (x >> 52)) - 0x3ff) * ((x & 0x000fffffffffffff) * 2^(-52) + 1)

Es wird sein. Beachten Sie, dass 0 in dieser Form nicht ausgedrückt werden kann, aber beim Multiplizieren eine wichtige Rolle spielt

% ./float 0
double: 0.000000
hex: 0000000000000000

Und alle Bits werden auf 0 gesetzt.

Zusammenfassung

Die Fraktion wurde in Form von ausgedrückt.

Zusatz

Drucken wir den Wert in der Mitte durch die Berechnung der 0,1-fachen Addition von 0,1, die wir am Anfang gesehen haben.

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

Zunächst sollte die erste Zeile die Dezimalzahl 0,1 darstellen.

2^((0x3fb - 0x3ff) * (0x1999999999999a * 2^(-52))
= 0x1999999999999a * 2^(-56)
= 7205759403792794.0 / 72057594037927936.0

Es ist nicht möglich, 0,1 genau darzustellen (es ist etwas größer als 0,1). Wenn die Dezimalzahl 0,1 binär ausgedrückt wird, wird sie zu einem kreisförmigen Bruch, und in hexadezimal wird 9 unbegrenzt fortgesetzt, aber es zeigt, dass es einen Fehler gibt, sie in einer endlichen Anzahl von Ziffern auszudrücken.

Als nächstes sehen wir uns an, wie die Addition berechnet wird. Gleitkommazahlen wurden durch Exponenten und falsche Zahlen dargestellt, ihre Addition erfolgt jedoch dezimal.

10 * 3 + 100 * 5 = 100 * (0.3 + 5) = 100 * 5.3

Mach etwas ähnliches wie. In der Tat, wenn Sie einen Bruch in die Form schreiben (Exponent, unpassend),

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

Hier ist die Regel der Rundung beim Verschieben falscher Zahlen

Ich benutze

0x33333333333334 = ...00110100
1 Bitverschiebung →...1010 = 0x1999999999999a

0x999999999999a = ...10011010
1 Bitverschiebung →...1101 = 0xccccccccccccd

0x26666666666667 = ...01100111
1 Bitverschiebung →...0100 = 0x13333333333334

Wieder müssen wir runden, um den Exponenten mit 53 Bit darzustellen, und wir können sehen, dass es mehr Fehler gibt. Aufgrund dieser Fehler führt das 10-fache Hinzufügen von 0,1 zu weniger als 1.

Python-Bruchdarstellung

Jetzt, da ich die Darstellung von Gleitkommazahlen mit einem C-Sprachprogramm verstehe Stellen wir schließlich sicher, dass Python tatsächlich denselben Ausdruck verwendet.

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

Die Schreibmethode unterscheidet sich geringfügig, es werden jedoch dieselben Werte wie in der C-Sprache angezeigt.

Wenn Sie sich die CPython-Implementierung ansehen, wird der Python-Float-Wert durch ein C-Double dargestellt.

cpython/Include/floatobject.h


typedef struct {
    PyObject_HEAD
    double ob_fval;
} PyFloatObject;

Zusammenfassung

Recommended Posts

Python-Fraktion
Python
Kafka Python
Python-Grundlagen ⑤
Python-Zusammenfassung
Eingebaute Python
Python-Einschlussnotation
Python-Technik
Python studieren
Python 2.7 Countdown
Python-Memorandum
Python FlowFishMaster
Python-Dienst
Python-Tipps
Python-Funktion ①
Python-Grundlagen
Python-Memo
Ufo-> Python (3)
Python-Einschlussnotation
Installieren Sie Python
Python Singleton
Python-Grundlagen ④
Python-Memorandum 2
Python-Memo
Python Jinja2
Python-Inkrement
atCoder 173 Python
[Python] -Funktion
Python-Installation
Versuchen Sie Python
Python-Memo
Python iterativ
Python2 + word2vec
Python-Funktionen
Python-Tutorial
Python Underbar Das ist was
Python-Zusammenfassung
Starten Sie Python
[Python] Sortieren
Hinweis: Python
Python-Grundlagen ③
Python-Protokoll ausgeben
Python-Grundlagen
[Scraping] Python-Scraping
Python-Update (2.6-> 2.7)
Python-Memo
Python-Memorandum
Python #sort
Ufo-> Python
Python nslookup
Python lernen
[Rpmbuild] Python 3.7.3.
Prorate Python (1)
Python Memorandum
Laden Sie Python herunter
Python Memorandum
Python-Memo
Python gestartet