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.
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.
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.
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.
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;
Recommended Posts