[PYTHON] Warum ist die Gleitkommazahl von 0,1 größer als 0,1, aber wenn sie zehnmal addiert wird, ist sie kleiner als 1,0 [Teil 1]

In diesem Artikel habe ich festgestellt, dass die Konvertierung von "0.1" in eine Gleitkommazahl zu einer Zahl führt, die etwas größer als "0.1" ist. Warum wird 0,1 beim Drucken als 0,1 angezeigt, obwohl 0,1 nicht genau durch eine Gleitkommazahl dargestellt werden kann Das heißt, wenn Sie "0,1" hinzufügen, sollten sich positive Fehler ansammeln. Zum Beispiel habe ich erwartet, dass das 10-fache Hinzufügen von "0,1" etwas größer als "1" ist. Ich werde es versuchen.

total = 0

for i in range(10):
    total += 0.1

print(total)
#=> 0.9999999999999999

Unerwarteterweise wurde eine Zahl kleiner als "1" ausgegeben. Diesmal habe ich dieses Phänomen untersucht.

Umgebung

Dieser Artikel verwendet Python 3.7.

[Annahme] Gleitkommazahl

In diesem Artikel bezieht sich der Begriff "Gleitkommazahl" im Folgenden auf "IEEE 754-fache Genauigkeit".

Das Format von Gleitkommazahlen ist die Konvertierung von Zahlen in das folgende Format, in dem "Vorzeichen", "Exp" und "Frac" in der Reihenfolge angeordnet sind.

(-1)^{sign} \times 2^{exp - 1023} \times (1 + frac \times 2^{-52})

Der Name und die Anzahl der Bits jedes Symbols sind wie folgt.

Symbol Japanischer Name englischer Name Anzahl der Bits
sign Codeteil sign 1
exp Indexteil exponent 11
frac Formaler Teil fraction 52

Wenn 0,1 als Gleitkommazahl ausgedrückt wird

to_float_point_0_1.png https://tools.m-bsys.com/calculators/ieee754.php

0.1 ist die binäre Darstellung von Gleitkommazahlen

Code= 0
Index= 01111111011
Mantisse= 1001100110011001100110011001100110011001100110011010

Bei der Konvertierung in eine Dezimalzahl

Code= 0
Index= 1019
Mantisse= 2702159776422298

ist. Dem Gleitkomma-Exponenten wird "1023" hinzugefügt. Wenn Sie dies subtrahieren, erhalten Sie "-4".

Konvertieren von "0,1", das durch eine Gleitkommazahl dargestellt wird, in eine Dezimalzahl

0.1000000000000000055511151231257827021181583404541015625

ist.

Zahl beim Hinzufügen von jeweils 0,1

Die Gleitkommazahl von "0,1" ist etwas größer als "0,1", aber wenn sie beim 10-fachen Addieren nicht zu "1" wird, scheint irgendwo ein Umkehrphänomen aufzutreten. Lassen Sie uns das Berechnungsergebnis auf dem Weg sehen. Wenn Sie ein Gleitkomma an "Dezimal" und "Drucken" übergeben, wird der Wert angezeigt, wenn die Gleitkommazahl in eine Dezimalzahl konvertiert wird.

from decimal import Decimal

total = 0
d_total = Decimal('0')

for i in range(10):
    total += 0.1
    d_total += Decimal('0.1')
    print(f'{d_total}|{Decimal(total)}')

Hier ist das Ausführungsergebnis.

Dezimal Berechnungsergebnis
0.1 0.1000000000000000055511151231257827021181583404541015625
0.2 0.200000000000000011102230246251565404236316680908203125
0.3 0.3000000000000000444089209850062616169452667236328125
0.4 0.40000000000000002220446049250313080847263336181640625
0.5 0.5
0.6 0.59999999999999997779553950749686919152736663818359375
0.7 0.6999999999999999555910790149937383830547332763671875
0.8 0.79999999999999993338661852249060757458209991455078125
0.9 0.899999999999999911182158029987476766109466552734375
1.0 0.99999999999999988897769753748434595763683319091796875

0.1 bis 0.4 sind mehr als als Dezimalzahlen berechnet, 0.5 ist genau und 0.6 bis 1.0 sind mehr als als Dezimalzahlen berechnet. Das Ergebnis war, dass es auch klein war.

Überprüfen Sie beim Hinzufügen von jeweils 0,1, wie viel hinzugefügt wird

Lassen Sie uns nun überprüfen, wie viel tatsächlich hinzugefügt wird, wenn jeweils "0.1" hinzugefügt wird. Das Programm ist da. Pythons Dezimalzahl hat standardmäßig 28 gültige Ziffern. Ändern Sie sie daher im Voraus auf eine ausreichende Größe.

from decimal import Decimal, getcontext

#Die Anzahl der gültigen Dezimalstellen wurde auf 64 Stellen geändert
getcontext().prec = 64

total = prev = 0
d_total = Decimal('0')

for i in range(10):
    total += 0.1
    d_total += Decimal('0.1')
    print(f' |{Decimal(total) - Decimal(prev)}')
    print(f'{d_total}|{Decimal(total)}')
    prev = total

Dies ist das Ausführungsergebnis. Die Berechnungsergebnisse mit Gleitkommazahlen werden in den ungeraden Zeilen ohne die Überschrift angezeigt, und die Unterschiede zwischen ihnen werden in den geraden Zeilen angezeigt.

Dezimal Berechnungsergebnis / Differenz
0.1 0.1000000000000000055511151231257827021181583404541015625
0.1000000000000000055511151231257827021181583404541015625
0.2 0.200000000000000011102230246251565404236316680908203125
0.100000000000000033306690738754696212708950042724609375
0.3 0.3000000000000000444089209850062616169452667236328125
0.09999999999999997779553950749686919152736663818359375
0.4 0.40000000000000002220446049250313080847263336181640625
0.09999999999999997779553950749686919152736663818359375
0.5 0.5
0.09999999999999997779553950749686919152736663818359375
0.6 0.59999999999999997779553950749686919152736663818359375
0.09999999999999997779553950749686919152736663818359375
0.7 0.6999999999999999555910790149937383830547332763671875
0.09999999999999997779553950749686919152736663818359375
0.8 0.79999999999999993338661852249060757458209991455078125
0.09999999999999997779553950749686919152736663818359375
0.9 0.899999999999999911182158029987476766109466552734375
0.09999999999999997779553950749686919152736663818359375
1.0 0.99999999999999988897769753748434595763683319091796875

Die Differenz von "0,1 → 0,2" ist die gleiche wie der Wert von "0,1", ausgedrückt als Gleitkommazahl, aber "0,2 → 0,3" ist größer. Nach "0,3" wird derselbe Wert addiert, der kleiner ist als der Wert von "0,1", ausgedrückt als Gleitkommazahl.

Übrigens werden nicht alle Werte kleiner als "0.1" nach "0.4" addiert, sondern mit "1.1 → 1.2" wieder erhöht.

Erwarteter Wert Berechnungsergebnis / Differenz
1.0 0.99999999999999988897769753748434595763683319091796875
0.09999999999999997779553950749686919152736663818359375
1.1 1.0999999999999998667732370449812151491641998291015625
0.1000000000000000888178419700125232338905334472656250
1.2 1.1999999999999999555910790149937383830547332763671875
0.1000000000000000888178419700125232338905334472656250
1.3 1.3000000000000000444089209850062616169452667236328125
0.1000000000000000888178419700125232338905334472656250
1.4 1.4000000000000001332267629550187848508358001708984375
0.1000000000000000888178419700125232338905334472656250
1.5 1.5000000000000002220446049250313080847263336181640625

Warum erhöht sich "0,1" nicht um die Anzahl der Gleitkommawerte, wenn "0,1" hinzugefügt wird? Es geht um Rundungsfehler.

Verfahren zum Hinzufügen von Gleitkommazahlen

Das Hinzufügen positiver Gleitkommazahlen erfolgt nach dem folgenden Verfahren.

① Passen Sie den kleineren Index an den größeren Index an (2) Passen Sie die Einstellung an, indem Sie die falsche Anzahl verringern, wenn der Exponent erhöht wird. ③ Fügen Sie die falschen Nummern hinzu ④ Wenn ein Übertrag auftritt, addieren Sie 1 zum Exponenten und reduzieren Sie die falsche Zahl entsprechend. ⑤ Runden Sie die überlaufenden Ziffern der falschen Zahl (gerade Rundung).

Beispiel für eine Addition in Dezimalzahl

Das Verfahren zum Hinzufügen von Gleitkommazahlen wird anhand eines Beispiels für Dezimalzahlen gezeigt. Betrachten Sie die Hinzufügung von 5-stelligen Dezimalzahlen.

\begin{array}{llcll}
    &9.8192 & \times & 10^2 & (= 981.92)\\
 + &4.7533 & \times & 10^1 & (= 47.533)\\
\hline
\end{array}

Das Verfahren wird unten erklärt.

① Passen Sie den kleineren Index an den größeren Index an (2) Passen Sie die Einstellung an, indem Sie die falsche Anzahl verringern, wenn der Exponent erhöht wird.

Richten Sie die Exponenten für die Addition aus. Diesmal ist der mit dem größeren Index "9,8192 x 10 ^ 2", also werden wir "4,7533 x 10 ^ 1" transformieren, um dies anzupassen.

\begin{array}{llcr}
&4.7533 & \times & 10^1\\
 = &0.47533 & \times & 10^2
\end{array}

③ Fügen Sie die falschen Nummern hinzu Addieren.

\begin{array}{llcr}
    &\phantom{1}9.8192 & \times & 10^2\\
 + &\phantom{1}0.47533 & \times & 10^2\\
\hline
 & 10.29453 & \times & 10^2
\end{array}

④ Wenn ein Übertrag auftritt, addieren Sie 1 zum Exponenten und reduzieren Sie die falsche Zahl entsprechend.

Da der ganzzahlige Teil 10 ist, tritt ein Übertrag auf. Um den ganzzahligen Teil zu einer Ziffer zu machen, addieren Sie 1 zum Exponenten und teilen Sie die falsche Zahl durch 10.

\begin{array}{llcr}
&10.29453 & \times & 10^2\\
 = &1.029453 & \times & 10^3
\end{array}

⑤ Rund um den überlaufenden Träger (gleichmäßig rund)

Dieses Mal beträgt die Anzahl der gültigen Ziffern 5, daher müssen wir die Verarbeitung der nachfolgenden "53" berücksichtigen. Da hier sogar eine Rundung verwendet wird, wird diese aufgerundet.

\begin{array}{llcr}
&1.029453 & \times & 10^3\\
\rightarrow &1.0295 & \times & 10^3
\end{array}

Das Verfahren zum Hinzufügen von Gleitkommazahlen wurde in Dezimalzahlen bestätigt.

Gleichmäßiges Runden

Gerade Rundung ist eine Rundungsmethode ähnlich der Rundung. Wenn sich das zu rundende Objekt jedoch genau in der Mitte der beiden Werte befindet, wird es auf- oder abgerundet, sodass die obere Ziffer gerade ist.

Wenn Sie beispielsweise eine gerade Zahl auf die erste Ziffer runden, wird "1,5" zu "2" und "4,5" zu "4". 4.51 ist 5.

Schutzträger und klebriges Gebiss

Im Beispiel des Addierens in Dezimalzahl ist die letzte Ziffer ( 53 im obigen Beispiel) von den effektiven Ziffern übergelaufen, als die Exponenten kombiniert wurden. Diese Werte sollten beiseite gelegt werden, da sie am Ende gerundet werden. (Schneiden Sie nicht ab, nur weil der Exponent erhöht und die falsche Anzahl verringert und in Schritt 2 angepasst wurde.)

Mit gleichmäßiger Rundung,

-Größer als 5 → Aufrunden ―― 5 Perfekt → In gleichmäßiger Richtung auf- oder abrunden ―― Weniger als 5 → Abschneiden

Daher benötigen wir Informationen zu den darunter liegenden Ziffern sowie die höchstwertige Ziffer der überlaufenden Ziffern. Für Informationen zu anderen Ziffern als der höchstwertigen Ziffer wird jedoch benötigt, ob sie genau 0 sind oder nicht. Daher scheint es ausreichend zu sein, einen Booleschen Wert vorzubereiten.

Schutzziffern sind der Bereich, in dem die überlaufenden Ziffern gespeichert werden. Für die Addition positiver Zahlen reicht eine Schutzziffer aus. (Ein weiteres Bit wird benötigt, wenn es um Subtraktion und negative Zahlen geht). Wenn die Schutzziffer eine Ziffer ist, wird im obigen Beispiel das höchstwertige Bit "5" der überlaufenden Ziffern "53" gespeichert. Ein boolescher Wert, der angibt, ob eine Zahl kleiner oder gleich der Schutzziffer eine Zahl größer oder gleich 1 enthält, wird als Sticky-Bit bezeichnet. Im obigen Beispiel ist die überlaufende Ziffer "3" 1 oder mehr, daher wird "wahr" (oder "1") gesetzt.

Schreiben Sie die Addition in Dezimalzahl mit geschützten Ziffern und klebrigen Bits neu

Wenden Sie die Schutzziffern und Haftbits auf das obige Beispiel für die Dezimaladdition an.

\begin{array}{llcll}
    &9.8192 & \times & 10^2 & (= 981.92)\\
 + &4.7533 & \times & 10^1 & (= 47.533)\\
\hline
\end{array}

① Passen Sie den kleineren Index an den größeren Index an (2) Passen Sie die Einstellung an, indem Sie die falsche Anzahl verringern, wenn der Exponent erhöht wird.

\begin{array}{lrcll}
&4.7533 & \times & 10^1&\\
 = &0.47533 & \times & 10^2&(Schutzträger=3)
\end{array}

Da die letzte 3 übergelaufen ist, habe ich sie in der Schutzziffer gespeichert.

③ Fügen Sie die falschen Nummern hinzu

\begin{array}{llcr}
    &\phantom{1}9.8192 & \times & 10^2&\\
 + &\phantom{1}0.4753 & \times & 10^2&(Schutzträger=3)\\
\hline
 & 10.2945 & \times & 10^2&(Schutzträger=3)
\end{array}

④ Wenn ein Übertrag auftritt, addieren Sie 1 zum Exponenten und reduzieren Sie die falsche Zahl entsprechend.

\begin{array}{llcrl}
&10.2945 & \times & 10^2&\\
 = &1.0294 & \times & 10^3&(Schutzträger=5,Klebriges Stück=true)
\end{array}

Die nachfolgende "5" ist übergelaufen, daher habe ich sie in der Schutzziffer gespeichert. Zu diesem Zeitpunkt wird 3, das ursprünglich in der Schutzziffer gespeichert war, im Sticky-Bit gespeichert. Das Sticky-Bit gibt an, ob die Zahl 1 oder mehr ist, also wird "true" gesetzt.

⑤ Rund um den überlaufenden Träger (gleichmäßig rund) Verwenden Sie zum Abrunden den Schutzträger und das Klebeband. Das Aufrunden wird durchgeführt, weil das klebrige Bit mit der Schutzziffer 00 wahr ist.

\begin{array}{llcrl}
&1.0294 & \times & 10^3&(Schutzträger=5,Klebriges Stück=true)\\
\rightarrow &1.0295 & \times & 10^3
\end{array}

Das Verfahren zum Hinzufügen von Gleitkommazahlen mit geschützten Ziffern und Haftbits wurde in Dezimalzahlen bestätigt. Machen Sie dasselbe für Binärzahlen.

Zum zweiten Teil

Da es lang geworden ist, werde ich den Artikel teilen. Im zweiten Teil werden wir das Verhalten beim Hinzufügen von "0.1" überprüfen. Die Websites, auf die ich mich bezog, sind auch im zweiten Teil zusammengefasst.

Warum ist die Gleitkommazahl von 0,1 größer als 0,1, aber kleiner als 1,0, wenn sie zehnmal hinzugefügt wird [Teil 2]

Recommended Posts

Warum ist die Gleitkommazahl von 0,1 größer als 0,1, aber wenn sie zehnmal addiert wird, ist sie kleiner als 1,0 [Teil 1]
Warum ist die Gleitkommazahl von 0,1 größer als 0,1, aber wenn sie zehnmal addiert wird, ist sie kleiner als 1,0 [Teil 2]
Warum wird 0,1 beim Drucken als 0,1 angezeigt, obwohl 0,1 nicht durch eine Gleitkommazahl genau dargestellt werden kann?
Überprüfen Sie die speicherinterne Byte-Zeichenfolge der Gleitkommazahl in Python
Es scheint, dass die Version von Pyflakes nicht die neueste ist, wenn flake8 installiert ist
Tiefes Lernen! Die Geschichte der Daten selbst, die gelesen werden, wenn sie nach der handschriftlichen Nummernerkennung nicht folgen