Heim  >  Artikel  >  Backend-Entwicklung  >  Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

青灯夜游
青灯夜游nach vorne
2021-07-15 18:53:0210390Durchsuche

In diesem Artikel wird anhand von Python erläutert, warum Gleitkommaoperationen Fehler verursachen. Bitte erläutern Sie die Umstände, unter denen Fehler auftreten können. und wie kann man es lösen? Hoffe es hilft.

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

[Verwandte Empfehlung: Python3-Video-Tutorial]

Jeder wird beim Schreiben von Code auf den sogenannten Gleitkommafehler stoßen, ich kann das nur sagen Du hast zu viel Glück.

Nehmen Sie das Python-Bild unten als Beispiel: 0,1 + 0,2 ist nicht gleich 0,3 und 8,7 / 10 ist nicht gleich 0,87 Code>, aber 0,869999…, es ist so seltsam0.1 + 0.2 并不等于 0.38.7 / 10 也不等于 0.87,而是 0.869999…,真是太奇怪了

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

但这绝对不是什么阴间 bug,也不是 Python 设计得有问题,而是浮点数在做运算时必然的结果,所以即便是在 JavaScript 或其他语言中也都是一样:

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

电脑是怎样储存一个整数的(Integer)

在讲为什么会存在浮点误差之前,先来谈谈电脑是怎么用 0 跟 1 来表示一个 整数 的,大家应该都知道二进制:例如 101 代表 ^2 + 2^0$ 也就是 5、1010 代表 ^3 + 2^1$  也就是 10。

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

如果是一个无符号的 32 bit 整数,代表它有 32 个位置可以放 0 或 1,所以最小值就是 0000...0000 也就是 0,而最大值 1111...1111 代表  ^{31} + 2^{30} + ... + 2^1 + 2^0$ 也就是 4294967295

从排列组合的角度来看,因为每一个 bit 位都可以是 0 或 1,整个变量的值有 ^{32}$ 种可能,所以可以 精确的 表达出 0 到 ^{23} - 1$ 之间的任一个值,不会有任何误差。

浮点数(Floating Point)

虽然从 0 到 ^{23} - 1$ 之间存在很多个整数,但其数量终究是 有限 的,就是 ^{32}$ 那么多个而已;但浮点数就不同了,我们可以这样想:在 1 到 10 这个区间中只有十个整数,却有 无穷多个 浮点数,例如 5.1、5.11、5.111 等等,怎么也列举不完。

但因为在 32 bit 的空间中就只有 2³² 种可能性,为了把所有浮点数都塞在这个 32 bit 的空间里面,许多 CPU 厂商发明了各种浮点数的表示方式,但如果每家 CPU 的格式都不一样也很麻烦,所以最后是以 IEEE 发布的 IEEE 754 作为通用的浮点数运算标准,现在的 CPU 也都遵循这个标准进行设计。

IEEE 754

IEEE 754 里面定义了很多东西,其中包括单精度(32 bit)、双精度(64 bit)和特殊值(无穷大、NaN)的表示方式等等

规格化

以 8.5 这个浮点数来说,如果要变成 IEEE 754 格式的话必须先做一些规格化处理:把 8.5 拆成 8 + 0.5 也就是 ^3 + (cfrac{1}{2})^1$ ,接着写成二进位变成 1000.1,最后再写成 .0001 times 2^3$,与十进制的科学记数法很相似。

单精度浮点数

在 IEEE 754 中 32 bit 浮点数被拆分成三个部分,分别是 数符(sign)、阶码(exponent) 和尾数(fraction),加起来总共是 32 个 bit

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

  • 数符(sign):最左侧的 1 bit 代表正负号,正数的话 sign 就为 0,反之则是 1
  • 阶码(exponent):中间的 8 bit 代表规格化之后的次方数,采用的是 阶码真值 +127 的格式,也就是 3 还要再加上 127 等于 130
  • 尾数(fraction):最右侧的 23 bit 放的是小数部分,以 1.0001 来说就是去掉 1. 之后的 0001
  • Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

Aber das ist definitiv kein Fehler in der Unterwelt, noch ist es ein Problem mit Pythons Design, sondern eine unvermeidliche Folge von Gleitkommazahlen Zahlen beim Ausführen von Operationen, also auch wenn das Gleiche in JavaScript oder anderen Sprachen gilt:

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugenEine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

Wie der Computer eine Ganzzahl (Integer) speichert

Bevor wir darüber sprechen, warum Gleitkommafehler auftreten, wollen wir zunächst darüber sprechen, wie das geschieht Der Computer verwendet 0 und 1, um eine Ganzzahl darzustellen. Jeder sollte die Binärzahl kennen: Beispielsweise steht 101 für $2^2 + 2^0$, also 5, 1010 stellt $2^3 + 2^ 1$ dar und ist 10.

Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

Wenn Es handelt sich um eine vorzeichenlose 32-Bit-Ganzzahl, was bedeutet, dass sie 32 Positionen hat, an denen 0 oder 1 platziert werden können. Der Mindestwert ist also 0000...0000, also 0, und der Höchstwert ist 1111.. .1111 stellt $2^{31} + 2^{30} + ... + 2^1 + 2^0$ dar, was 4294967295 ist🎜🎜Aus der Perspektive der Permutation und Kombination, weil jedes Bit Es kann 0 oder 1 sein. Der Wert der gesamten Variablen hat $2^{32}$ Möglichkeiten, sodass sie 🎜genau 🎜 jeden Wert zwischen 0 und $2^{23} - 1$ ohne Fehler ausdrücken kann . . 🎜

🎜Gleitkommazahl🎜

🎜Obwohl es viele ganze Zahlen zwischen 0 und $2^{23} - 1$ gibt, ist ihre Anzahl schließlich 🎜begrenzt, nämlich $2^{ 32}$ sind genau so viele ; aber Gleitkommazahlen sind anders kann sie nicht alle aufzählen. 🎜🎜Aber da es im 32-Bit-Raum nur 2³² Möglichkeiten gibt, haben viele CPU-Hersteller verschiedene Methoden zur Darstellung von Gleitkommazahlen erfunden, um alle Gleitkommazahlen in diesen 32-Bit-Raum zu stopfen Da alle Formate unterschiedlich und umständlich sind, wurde am Ende der von IEEE veröffentlichte Standard IEEE 754 als allgemeiner Gleitkomma-Berechnungsstandard verwendet. Heutige CPUs basieren ebenfalls auf diesem Standard. 🎜

🎜IEEE 754🎜

🎜IEEE 754 definiert viele Dinge, einschließlich Darstellungsmethoden mit einfacher Genauigkeit (32 Bit), doppelter Genauigkeit (64 Bit) und Sonderwertdarstellungen (unendlich, NaN) usw. 🎜 🎜🎜 🎜Normalisierung🎜🎜🎜🎜 Nehmen wir als Beispiel die Gleitkommazahl 8,5: Wenn Sie sie in das IEEE 754-Format ändern möchten, müssen Sie zunächst eine Normalisierungsverarbeitung durchführen: 8,5 in 8 + 0,5 aufteilen, was $2^3 + (cfrac {1 }{2})^1$, dann binär geschrieben, um 1000.1 zu werden, und schließlich als $1,0001 mal 2^3$ geschrieben, was der wissenschaftlichen Dezimalschreibweise sehr ähnlich ist. 🎜🎜🎜🎜Gleitkommazahlen mit einfacher Genauigkeit🎜🎜🎜🎜In IEEE 754 werden 32-Bit-Gleitkommazahlen in drei Teile aufgeteilt, nämlich Vorzeichen, Exponent und Bruch, was insgesamt 32 Bit ergibt🎜🎜🎜
  • Vorzeichen: Das ganz linke 1-Bit stellt das positive und negative Vorzeichen dar. Wenn es positiv ist, ist das Vorzeichen 0, andernfalls ist es 1🎜
  • Exponent: Das mittlere 8-Bit stellt die Potenz nach der Normalisierung dar, unter Verwendung des Formats von 🎜Code wahrer Wert + 127🎜, das heißt 3 plus 127 ergibt 130🎜
  • Mantisse (Bruch): die 23 Bits ganz rechts. Was eingegeben wird, ist der Dezimalteil. Beispielsweise ist 1,0001 der 0001 nach dem Entfernen von 1.🎜🎜🎜Also wenn 8.5 im 32-Bit-Format ausgedrückt wird, sollten die Wörter wie folgt lauten: 🎜🎜🎜🎜🎜🎜🎜Unter welchen Umständen wird Fehler auftreten? 🎜🎜🎜🎜Das oben erwähnte Beispiel von 8,5 kann als $2^3 + (cfrac{1}{2})^1$ ausgedrückt werden, da 8 und 0,5 Potenzen von 2 sind, sodass es kein Genauigkeitsproblem gibt. 🎜

    Aber wenn es 8,9 ist, gibt es keine Möglichkeit, Potenzen von 2 zu addieren, daher wird es gezwungen, als $1,0001110011... mal 2^3$ ausgedrückt zu werden, und es wird auch ein Fehler von etwa $0,0000003$ auftreten Wenn Sie neugierig auf das Ergebnis sind, können Sie auf der Website des IEEE-754 Floating Point Converter herumspielen.

    Gleitkommazahlen mit doppelter Genauigkeit

    Die zuvor erwähnten Gleitkommazahlen mit einfacher Genauigkeit verwenden nur 32 Bit, um den Fehler zu verringern, definiert IEEE 754 auch, wie 64 Bit verwendet werden Um Gleitkommazahlen darzustellen, was 32 Bit entspricht, hat sich der Bruchteil von 23 Bit auf 52 Bit mehr als verdoppelt, sodass die Genauigkeit natürlich erheblich verbessert wird.

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    Nehmen Sie gerade 8,9 als Beispiel. Obwohl es genauer sein kann, wenn es in 64 Bit ausgedrückt wird, da 8,9 nicht vollständig als Summe von Zweierpotenzen geschrieben werden kann, tritt beim Erreichen von 16 Dezimalstellen immer noch ein Fehler auf Allerdings mit Der Fehler mit einfacher Genauigkeit von 0,0000003 ist im Vergleich viel kleiner

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    Ähnliche Situationen umfassen 1.0 und 0,999...999 in Python, was sind gleich. 123 und 122.999...999 sind ebenfalls gleich, da die Lücke zwischen ihnen zu klein ist, um in Bruchzahlen dargestellt zu werden, also aus dem Binärformat Sie sind binär. Die Bits sind alle gleich. 1.00.999...999 是相等的、123122.999...999 也是相等的,因为他们之间的差距已经小到无法放在 fraction 里面,所以从二进制格式看来它们每一个二进制位都是一样的。

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    解决方法

    既然浮点数的误差是无法避免的,那就只好跟它共处了,下面是两个比较常见的处理方法:

    设定最大允许误差 ε (epsilon)

    在某些语言会提供所谓的 epsilon,用来让你判断是不是在浮点误差的允许范围内,以 Python 来说 epsilon 的值大约是 .2e^{-16}$

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    所以你可以把 0.1 + 0.2 == 0.3 改写成 0.1 + 0.2 — 0.3

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    Lösung

    Da der Fehler von Gleitkommazahlen unvermeidbar ist, müssen wir damit leben. Im Folgenden sind zwei gängige Verarbeitungsmethoden aufgeführt:

    Stellen Sie den maximal zulässigen Fehler ε (Epsilon) ein

    In einigen Sprachen wird das sogenannte Epsilon bereitgestellt, damit Sie beurteilen können, ob es innerhalb des zulässigen Bereichs des Gleitkommafehlers liegt. In Python beträgt der Wert von Epsilon ungefähr $2,2e^{-16}$

    Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen Eine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen

    Damit Sie 0,1 + 0,2 == 0,3 in 0,1 + 0,2 – 0,3 Dies verhindert, dass Gleitkommafehler während der Operation Probleme verursachen, und gewährleistet einen korrekten Vergleich 0,2 gleich 0,3?

    Wenn das System es nicht zur Verfügung stellt, können Sie natürlich auch selbst ein Epsilon definieren und es auf etwa 2 hoch -15 einstellen Der Grund für den Gleitkommafehler liegt darin, dass es bei der Konvertierung von Dezimalzahlen in Binärzahlen keine Möglichkeit gibt, alle Dezimalteile in die Mantisse zu stopfen. Da es bei der Konvertierung zu Fehlern kommen kann, führen wir die Berechnungen einfach nicht mit Dezimalzahlen durch direkt. In Python gibt es ein Modul namens „decimal“ und in JavaScript gibt es ein ähnliches Paket. Es kann Ihnen dabei helfen, Berechnungen im Dezimalformat durchzuführen, so wie Sie 0,1 + 0,2 mit Stift und Papier ohne Fehler oder Fehler berechnen können.

    🎜🎜🎜🎜Obwohl durch die Verwendung von Dezimalberechnungen Fehler bei Gleitkommazahlen vollständig vermieden werden können, da die Dezimalberechnungen von Decimal simuliert werden, werden Binärberechnungen immer noch in der CPU-Schaltung der untersten Ebene verwendet und die Ausführung ist langsamer als die nativen Gleitkommaberechnungen. Punktoperationen sind viel langsamer, daher wird nicht empfohlen, Decimal für alle Gleitkommaoperationen zu verwenden. 🎜🎜Weitere Kenntnisse zum Thema Programmierung finden Sie unter: 🎜Einführung in die Programmierung🎜! ! 🎜

Das obige ist der detaillierte Inhalt vonEine kurze Diskussion darüber, warum Gleitkommaoperationen Fehler erzeugen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen