math - number - rechnen mit gleitkommazahlen




Ist die Gleitkomma-Mathematik defekt? (18)

Die Perspektive eines Hardware-Designers

Ich glaube, ich sollte dazu die Perspektive eines Hardware-Designers hinzufügen, da ich Gleitkomma-Hardware entwerfe und baue. Wenn Sie wissen, wo der Fehler liegt, kann dies hilfreich sein, um zu verstehen, was in der Software passiert. Letztendlich hoffe ich, dass dies die Gründe dafür erklärt, warum Gleitkommafehler auftreten und sich im Laufe der Zeit anhäufen.

1. Übersicht

Aus technischer Sicht haben die meisten Gleitkommaoperationen einige Fehler, da die Hardware, die die Gleitkommaberechnungen durchführt, an letzter Stelle nur einen Fehler von weniger als einer Hälfte einer Einheit haben muss. Daher wird eine Menge Hardware bei einer Genauigkeit enden, die nur erforderlich ist, um einen Fehler von weniger als der Hälfte einer Einheit an letzter Stelle für eine einzelne Operation zu erzielen, was insbesondere bei der Fließkommadivision problematisch ist. Was eine einzelne Operation ausmacht, hängt davon ab, wie viele Operanden die Einheit benötigt. Für die meisten sind es zwei, aber einige Einheiten benötigen 3 oder mehr Operanden. Aus diesem Grund kann nicht garantiert werden, dass wiederholte Operationen zu einem wünschenswerten Fehler führen, da sich die Fehler mit der Zeit summieren.

2. Standards

Die meisten Prozessoren folgen dem IEEE-754 Standard, aber einige verwenden denormalisierte oder andere Standards. Beispielsweise gibt es in IEEE-754 einen denormalisierten Modus, der die Darstellung sehr kleiner Fließkommazahlen auf Kosten der Genauigkeit ermöglicht. Im Folgenden wird jedoch der normalisierte Modus von IEEE-754 behandelt, der den typischen Betriebsmodus darstellt.

Im IEEE-754-Standard dürfen Hardware-Entwickler jeden Wert für error / epsilon angeben, sofern dieser weniger als eine Hälfte einer Einheit an letzter Stelle ist und das Ergebnis nur weniger als eine Hälfte einer Einheit sein muss Platz für eine Operation. Dies erklärt, warum sich die Fehler bei wiederholten Operationen addieren. Bei IEEE-754-Doppelgenauigkeit ist dies das 54. Bit, da 53 Bits verwendet werden, um den numerischen Teil (normalisiert), auch Mantisse genannt, der Fließkommazahl darzustellen (z. B. die 5.3 in 5.3e5). In den nächsten Abschnitten werden die Ursachen von Hardwarefehlern bei verschiedenen Gleitkommaoperationen genauer beschrieben.

3. Ursache für Rundungsfehler in der Division

Die Hauptursache für den Fehler in der Gleitkommadivision sind die zur Berechnung des Quotienten verwendeten Divisionsalgorithmen. Die meisten Computersysteme berechnen die Division unter Verwendung der Multiplikation mit einer Inversen, hauptsächlich in Z=X/Y , Z = X * (1/Y) . Eine Division wird iterativ berechnet, dh jeder Zyklus berechnet einige Bits des Quotienten, bis die gewünschte Genauigkeit erreicht ist, was für IEEE-754 irgendetwas mit einem Fehler von weniger als einer Einheit an letzter Stelle ist. Die Tabelle der Kehrwerte von Y (1 / Y) ist in der langsamen Division als Quotientenauswahltabelle (QST) bekannt, und die Größe der Bits der Quotientenauswahltabelle ist normalerweise die Breite der Basis oder eine Anzahl von Bits der in jeder Iteration berechnete Quotient plus ein paar Schutzbits. Für den IEEE-754-Standard mit doppelter Genauigkeit (64 Bit) wäre dies die Größe der Basis des Teilers plus ein paar Schutzbits k, wobei k>=2 . Eine typische Quotienten-Auswahltabelle für einen Teiler, der jeweils 2 Bits des Quotienten (Radix 4) berechnet, wäre beispielsweise 2+2= 4 Bits (plus einige optionale Bits).

3.1 Rundungsfehler der Division: Annäherung an das Reziproke

Was in der Quotienten-Auswahltabelle wechselseitig ist, hängt von der Divisionsmethode ab : langsame Division wie SRT-Division oder schnelle Division wie Goldschmidt-Division; Jeder Eintrag wird gemäß dem Divisionsalgorithmus modifiziert, um den geringstmöglichen Fehler zu erzielen. In jedem Fall sind jedoch alle Kehrwerte Annäherungen an den tatsächlichen Kehrwert und führen zu Fehlern. Sowohl das langsame als auch das schnelle Divisionsverfahren berechnen den Quotienten iterativ, dh in jedem Schritt werden einige Bits des Quotienten berechnet, dann wird das Ergebnis vom Dividenden abgezogen, und der Dividierer wiederholt die Schritte, bis der Fehler weniger als eine Hälfte beträgt Einheit an letzter Stelle. Langsame Teilungsmethoden berechnen eine feste Anzahl von Stellen des Quotienten in jedem Schritt und sind in der Regel kostengünstiger in der Erstellung, und schnelle Teilungsmethoden berechnen eine variable Anzahl von Ziffern pro Schritt und sind in der Regel teurer in der Erstellung. Der wichtigste Teil der Divisionsmethoden besteht darin, dass die meisten von ihnen auf wiederholte Multiplikation durch Annäherung eines Reziproken angewiesen sind, so dass sie fehleranfällig sind.

4. Rundungsfehler bei anderen Vorgängen: Abschneiden

Eine weitere Ursache für Rundungsfehler bei allen Operationen sind die unterschiedlichen Abschneidemodi der endgültigen Antwort, die IEEE-754 zulässt. Es wird abgeschnitten, auf Null abgerundet, auf den nächstgelegenen Wert (Standard), aufgerundet und gerundet. Alle Methoden führen für eine einzelne Operation an letzter Stelle ein Fehlerelement von weniger als einer Einheit ein. Im Laufe der Zeit und bei wiederholten Operationen fügt die Kürzung auch den resultierenden Fehler kumulativ hinzu. Dieser Verkürzungsfehler ist besonders problematisch bei der Exponentiation, die eine Form wiederholter Multiplikation beinhaltet.

5. Wiederholte Operationen

Da die Hardware, die die Gleitkommaberechnungen durchführt, nur ein Ergebnis mit einem Fehler von weniger als der Hälfte einer Einheit an letzter Stelle für eine einzelne Operation liefern muss, wird der Fehler bei wiederholten Operationen größer, wenn sie nicht überwacht wird. Dies ist der Grund dafür, dass Mathematiker bei Berechnungen, die einen begrenzten Fehler erfordern, Methoden wie das Verwenden der runden zu nächsten geraden Ziffer an letzter Stelle von IEEE-754 verwenden, da sich die Fehler im Laufe der Zeit mit größerer Wahrscheinlichkeit gegenseitig aufheben out und Intervallarithmetik kombiniert mit Variationen der Rundungsmodi nach IEEE 754 , um Rundungsfehler vorherzusagen und zu korrigieren. Aufgrund seines geringen relativen Fehlers im Vergleich zu anderen Rundungsmodi, auf die nächste gerade Zahl gerundet (an letzter Stelle), ist dies der Standardrundungsmodus von IEEE-754.

Beachten Sie, dass der voreingestellte Rundungsmodus, der letzten Stelle , die gerade auf die letzte gerade Zahl ist , einen Fehler von weniger als der Hälfte einer Einheit an letzter Stelle für eine Operation garantiert. Die alleinige Verwendung von Abschneiden, Aufrunden und Abrunden kann zu einem Fehler führen, der an letzter Stelle größer als die Hälfte einer Einheit ist, an letzter Stelle jedoch weniger als eine Einheit. Daher werden diese Modi nur empfohlen, wenn dies der Fall ist Wird in Intervallarithmetik verwendet.

6. Zusammenfassung

Kurz gesagt, der grundlegende Grund für die Fehler bei Fließkommaoperationen ist eine Kombination aus der Verkürzung der Hardware und der Verkürzung eines Kehrwerts bei Divisionen. Da der IEEE-754-Standard für eine einzelne Operation an letzter Stelle nur einen Fehler von weniger als der Hälfte einer Einheit erfordert, summieren sich die Gleitkommafehler bei wiederholten Operationen, sofern sie nicht korrigiert werden.

Betrachten Sie den folgenden Code:

0.1 + 0.2 == 0.3  ->  false
0.1 + 0.2         ->  0.30000000000000004

Warum passieren diese Ungenauigkeiten?


Nein, nicht gebrochen, aber die meisten Dezimalbrüche müssen approximiert werden

Zusammenfassung

Die Fließkomma-Arithmetik ist genau, leider passt sie nicht zu unserer üblichen Basis-10-Zahlendarstellung. Es stellt sich also heraus, dass wir oft Eingaben machen, die etwas von dem abweichen, was wir geschrieben haben.

Selbst einfache Zahlen wie 0,01, 0,02, 0,03, 0,04 ... 0,24 können nicht genau als binäre Brüche dargestellt werden. Wenn Sie 0,01, 0,02, 0,03 hochzählen, wird der erste in Basis 2 darstellbare Bruch erst bei 0,25 angezeigt . Wenn Sie es mit FP versucht haben, wäre Ihre 0.01 etwas abgeneigt gewesen. Die einzige Möglichkeit, 25 bis zu einer exakten Zahl von 0,25 hinzuzufügen, hätte eine lange Kette von Kausalität erfordert, die Wächter und Rundung umfasst. Es ist schwer vorherzusagen, also werfen wir unsere Hände hoch und sagen "FP ist ungenau", aber das stimmt nicht wirklich.

Wir geben der FP-Hardware ständig etwas, was in Basis 10 einfach erscheint, sich aber in Basis 2 wiederholt.

Wie ist es passiert?

Wenn wir in Dezimalzahlen schreiben, ist jeder Bruch (insbesondere jede abschließende Dezimalzahl) eine rationale Zahl der Form

a / (2 n x 5 m )

In binär erhalten wir nur den 2 n- Term, das heißt:

a / 2 n

Also in dezimal, können wir repräsentieren nicht 1 / 3 . Da 10 der Basis 2 als Hauptfaktor enthält, jede Zahl , die wir als eine Binärbruch schreiben auch sein kann als Basis 10 Fraktion geschrieben. Kaum etwas, das wir als Basis- 10- Bruch schreiben, ist jedoch binär darstellbar. Im Bereich von 0,01, 0,02, 0,03 ... 0,99 können in unserem FP-Format nur drei Zahlen dargestellt werden: 0,25, 0,50 und 0,75, da sie alle Zahlen 1/4, 1/2 und 3/4 sind mit einem Primfaktor, der nur den 2 n- Term verwendet.

In Basis 10 können wir nicht repräsentieren 1 / 3 . Aber im Binär-, können wir nicht 1 / 10 oder 1 / 3 .

Während also jeder binäre Bruch in Dezimalzahlen geschrieben werden kann, ist das Gegenteil nicht der Fall. Tatsächlich wiederholen sich die meisten Dezimalbrüche binär.

Damit klarkommen

Entwickler werden normalerweise angewiesen, <Epsilon- Vergleiche durchzuführen. Besser ist es, auf ganzzahlige Werte zu runden (in der C-Bibliothek: round () und roundf (), dh im FP-Format bleiben) und dann zu vergleichen. Durch das Aufrunden auf eine bestimmte Dezimalbruchlänge werden die meisten Probleme bei der Ausgabe gelöst.

Bei realen Zahlenproblemen (den Problemen, für die FP auf frühen, furchtbar teuren Computern entwickelt wurde) sind die physikalischen Konstanten des Universums und alle anderen Messungen nur einer relativ geringen Anzahl von signifikanten Zahlen bekannt, so dass das gesamte Problem Raum ist war sowieso "ungenau". FP "Genauigkeit" ist bei dieser Art von Anwendung kein Problem.

Das ganze Problem entsteht wirklich, wenn Leute versuchen, FP für die Bean-Zählung zu verwenden. Es funktioniert dafür, aber nur, wenn Sie an ganzzahligen Werten festhalten, welche Art von Punkt den Zweck der Verwendung besiegt. Aus diesem Grund haben wir alle diese Dezimalbruch-Softwarebibliotheken.

Ich liebe die Pizza-Antwort von , weil sie das eigentliche Problem beschreibt, nicht nur die üblichen Handzeichen über "Ungenauigkeit". Wenn FP einfach "ungenau" wäre, könnten wir das beheben und hätten es schon vor Jahrzehnten getan. Der Grund, den wir nicht haben, ist, dass das FP-Format kompakt und schnell ist und der beste Weg ist, viele Zahlen zu berechnen. Es ist auch ein Erbe aus dem Weltraumzeitalter und Wettrüsten und frühen Versuchen, große Probleme mit sehr langsamen Computern mit kleinen Speichersystemen zu lösen. (Manchmal einzelne Magnetkerne für 1-Bit-Speicher, aber das ist eine andere Geschichte. )

Fazit

Wenn Sie nur Beans in einer Bank zählen, funktionieren Softwarelösungen, die dezimale String-Darstellungen verwenden, einwandfrei. Quantenchromodynamik oder Aerodynamik ist auf diese Weise jedoch nicht möglich.


Gleitkomma-Rundungsfehler. 0,1 kann in Base-2 nicht so genau wie in Base-10 dargestellt werden, da der Primfaktor 5 nicht ausreicht. Genau wie 1/3 eine unendliche Anzahl von Stellen für die Dezimalzahl benötigt, ist es in Base-3 "0,1". 0,1 nimmt eine unendliche Anzahl von Ziffern in Base-2 an, während es nicht in Base-10 ist. Und Computer haben nicht unendlich viel Speicher.


Im Computer gespeicherte Gleitkommazahlen bestehen aus zwei Teilen, einer Ganzzahl und einem Exponenten, zu dem die Basis genommen und mit dem Ganzzahlteil multipliziert wird.

Wenn der Computer in der Basis 10 arbeiten würde, wäre 0.1 1 x 10⁻¹ ; 1 x 10⁻¹ , 0.2 2 x 10⁻¹ ; 2 x 10⁻¹ und 0.3 3 x 10⁻¹ 2 x 10⁻¹ ; 3 x 10⁻¹ . Ganzzahlige Mathematik ist einfach und genau, daher ergibt das Hinzufügen von 0.1 + 0.2 offensichtlich 0.3 .

Computer arbeiten normalerweise nicht in Basis 10, sondern in Basis 2. Für einige Werte können Sie immer noch genaue Ergebnisse erhalten. Beispiel: 0.5 ist 1 x 2⁻¹ und 0.25 ist 1 x 2⁻² diese Werte hinzufügen, erhalten Sie 3 x 2⁻² oder 0.75 . Genau.

Das Problem tritt mit Zahlen auf, die in der Basis 10 genau dargestellt werden können, aber nicht in der Basis 2. Diese Zahlen müssen auf das nächste Äquivalent gerundet werden. Unter der Annahme, dass das übliche IEEE-64-Bit-Fließkommaformat die nächstgelegene Zahl 0.1ist 3602879701896397 x 2⁻⁵⁵und die nächstliegende Zahl 0.2ist 7205759403792794 x 2⁻⁵⁵; Addiert man sie zusammen 10808639105689191 x 2⁻⁵⁵, erhält man einen exakten Dezimalwert von 0.3000000000000000444089209850062616169452667236328125. Fließkommazahlen werden im Allgemeinen zur Anzeige gerundet.


Zusätzlich zu den anderen richtigen Antworten möchten Sie möglicherweise die Skalierung Ihrer Werte in Betracht ziehen, um Probleme mit der Fließkomma-Arithmetik zu vermeiden.

Zum Beispiel:

var result = 1.0 + 2.0;     // result === 3.0 returns true

... anstatt:

var result = 0.1 + 0.2;     // result === 0.3 returns false

Der Ausdruck 0.1 + 0.2 === 0.3 gibt in JavaScript den 0.1 + 0.2 === 0.3 false zurück. Glücklicherweise ist die Ganzzahlarithmetik in Fließkommazahlen genau, sodass Dezimaldarstellungsfehler durch Skalierung vermieden werden können.

Als praktisches Beispiel wird zur Vermeidung von Fließkomma-Problemen, bei denen Genauigkeit von größter Bedeutung ist, 1 empfohlen, Geld als Ganzzahl für die Anzahl der Cents zu behandeln: 2550 Cent anstelle von 25.50 Dollar.

1 Douglas Crockford: JavaScript: Die guten Teile : Anhang A - Schreckliche Teile (Seite 105) .


Die meisten Antworten beziehen sich auf diese Frage in sehr trockener, technischer Hinsicht. Ich möchte dies in Form von Begriffen ansprechen, die normale Menschen verstehen können.

Stellen Sie sich vor, Sie versuchen Pizza zu schneiden. Sie haben einen Roboter-Pizzaschneider, der Pizzastücke genau halbieren kann. Es kann eine ganze Pizza halbieren oder eine vorhandene Scheibe halbieren, aber in jedem Fall ist die Halbierung immer genau.

Dieser Pizzaschneider hat sehr feine Bewegungen, und wenn Sie mit einer ganzen Pizza beginnen, halbieren Sie diese und halbieren Sie jedes Mal das kleinste Stück. Sie können die Halbierung 53-mal machen, bevor das Stück zu klein ist, um die hochgenauen Fähigkeiten zu erreichen . An diesem Punkt können Sie das sehr dünne Segment nicht mehr halbieren, sondern müssen es entweder so einschließen oder ausschließen, wie es ist.

Wie würden Sie nun alle Scheiben so schneiden, dass sich ein Zehntel (0,1) oder ein Fünftel (0,2) einer Pizza summieren? Denken Sie wirklich darüber nach und versuchen Sie es herauszufinden. Sie können sogar versuchen, eine echte Pizza zu verwenden, wenn Sie einen mythischen Präzisionspizzaschneider zur Hand haben. :-)

Die meisten erfahrenen Programmierer kennen natürlich die eigentliche Antwort, dh es gibt keine Möglichkeit, ein genaues Zehntel oder Fünftel der Pizza aus diesen Scheiben zusammenzusetzen, egal wie fein sie geschnitten werden. Sie können eine ziemlich gute Näherung durchführen, und wenn Sie die Näherung von 0,1 mit der Näherung von 0,2 addieren, erhalten Sie eine ziemlich gute Näherung von 0,3, aber es ist immer noch eine Näherung.

Bei Zahlen mit doppelter Genauigkeit (dh der Genauigkeit, mit der Sie Ihre Pizza 53-fach halbieren können), sind die Zahlen, die sofort kleiner und größer als 0,1 sind, 0,09999999999999999997399739939931132594682276248931884765625 und 0,100000000000000005515123128128288128288128358288458458458458258128458128458128128128128128128128128128128128128128128128128128128128128128128128128128128258258252000200020002000200020002000200020002000200020002. Letzteres ist etwas näher an 0,1 als das erstere, so dass ein numerischer Parser bei einer Eingabe von 0,1 den letzteren begünstigt.

(Der Unterschied zwischen diesen beiden Zahlen ist das "kleinste Slice", das wir entweder einschließen müssen, wodurch eine Aufwärtsvoreingenommenheit eingeführt wird, oder ausgeschlossen werden, wodurch eine ulp eingeführt wird. Der technische Begriff für die kleinste Slice ist ein ulp .)

Im Fall von 0,2 sind die Zahlen alle gleich und werden nur um den Faktor 2 erhöht. Wieder bevorzugen wir einen etwas höheren Wert als 0,2.

Beachten Sie, dass in beiden Fällen die Annäherungen für 0,1 und 0,2 eine leichte Aufwärtsneigung haben. Wenn wir genug von diesen Vorurteilen hinzufügen, drängen sie die Anzahl immer weiter von dem ab, was wir wollen, und im Fall von 0,1 + 0,2 ist der Vorurteil so hoch, dass die resultierende Zahl nicht mehr die nächste ist bis 0,3.

Sie ist in erster Linie, um einen Ort zu erreichen, und zwar, wenn Sie sich für ein Produkt einschalten.

PS Einige Programmiersprachen bieten auch Pizzaschneider, mit denen Scheiben in genau Zehntel geteilt werden können . Obwohl solche Pizzaschneider ungewöhnlich sind, sollten Sie sie verwenden, wenn Sie Zugang zu einem haben, wenn es wichtig ist, genau ein Zehntel oder ein Fünftel einer Scheibe zu erhalten.

(Ursprünglich auf Quora veröffentlicht.)


Math.sum (Javascript) .... Art des Betreiberersatzes

.1 + .0001 + -.1 --> 0.00010000000000000286
Math.sum(.1 , .0001, -.1) --> 0.0001
Object.defineProperties(Math, {
    sign: {
        value: function (x) {
            return x ? x < 0 ? -1 : 1 : 0;
            }
        },
    precision: {
        value: function (value, precision, type) {
            var v = parseFloat(value), 
                p = Math.max(precision, 0) || 0, 
                t = type || 'round';
            return (Math[t](v * Math.pow(10, p)) / Math.pow(10, p)).toFixed(p);
        }
    },
    scientific_to_num: {  // this is from https://gist.github.com/jiggzson
        value: function (num) {
            //if the number is in scientific notation remove it
            if (/e/i.test(num)) {
                var zero = '0',
                        parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
                        e = parts.pop(), //store the exponential part
                        l = Math.abs(e), //get the number of zeros
                        sign = e / l,
                        coeff_array = parts[0].split('.');
                if (sign === -1) {
                    num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
                } else {
                    var dec = coeff_array[1];
                    if (dec)
                        l = l - dec.length;
                    num = coeff_array.join('') + new Array(l + 1).join(zero);
                }
            }
            return num;
         }
     }
    get_precision: {
        value: function (number) {
            var arr = Math.scientific_to_num((number + "")).split(".");
            return arr[1] ? arr[1].length : 0;
        }
    },
    diff:{
        value: function(A,B){
            var prec = this.max(this.get_precision(A),this.get_precision(B));
            return +this.precision(A-B,prec);
        }
    },
    sum: {
        value: function () {
            var prec = 0, sum = 0;
            for (var i = 0; i < arguments.length; i++) {
                prec = this.max(prec, this.get_precision(arguments[i]));
                sum += +arguments[i]; // force float to convert strings to number
            }
            return Math.precision(sum, prec);
        }
    }
});

Die Idee ist die Verwendung von Math statt Operatoren, um Float-Fehler zu vermeiden

Math.diff(0.2, 0.11) == 0.09 // true
0.2 - 0.11 == 0.09 // false

Beachten Sie auch, dass Math.diff und Math.sum die zu verwendende Genauigkeit automatisch erkennen

Math.sum akzeptiert eine beliebige Anzahl von Argumenten


Dies war eigentlich als Antwort auf diese Frage gedacht, die als Duplikat dieser Frage geschlossen wurde, während ich diese Antwort zusammenstellte, also kann ich sie jetzt nicht dort posten ... also werde ich stattdessen hier posten!

Zusammenfassung der Frage:

Auf dem Arbeitsblatt 10^-8/1000und 10^-11als gleich bewerten, während in VBA sie nicht.

In dem Arbeitsblatt werden die Zahlen standardmäßig in wissenschaftliche Notation gesetzt.

Wenn Sie die Zellen in ein Zahlenformat ( Ctrl+ 1) Numbermit 15Dezimalzeichen ändern , erhalten Sie Folgendes:

=10^-11 returns 0.000000000010000
=10^(-8/1000) returns 0.981747943019984

Also sind sie definitiv nicht gleich ... der eine ist nur etwa null und der andere nur etwa 1.

Excel war nicht für extrem kleine Zahlen konzipiert - zumindest nicht für die Aktieninstallation. Es gibt Add-Ins, die zur Verbesserung der Zahlengenauigkeit beitragen.

Excel wurde gemäß dem IEEE-Standard für binäre Gleitkomma-Arithmetik ( IEEE 754 ) entwickelt. Der Standard definiert, wie Fließkommazahlen gespeichert und berechnet werden. Der IEEE 754- Standard ist weit verbreitet, da er ermöglicht, Fließkommazahlen in einem angemessenen Raum zu speichern und Berechnungen relativ schnell durchzuführen.

Der Vorteil des Gleitens über einer festen Punktdarstellung besteht darin, dass ein größerer Wertebereich unterstützt werden kann. Zum Beispiel kann eine Festpunktdarstellung , die 5 Dezimalziffern mit dem Dezimalpunkt nach der dritten Stelle positioniert hat , kann die Zahlen darstellen 123.34, 12.23, 2.45usw. während Gleitkommadarstellung mit 5 - stellige Genauigkeit repräsentieren 1,2345 kann, 12345, 0,00012345, usw. In ähnlicher Weise ermöglicht die Gleitkommadarstellung auch Berechnungen über einen weiten Bereich von Größenordnungen, während die Genauigkeit beibehalten wird. Zum Beispiel,

Weitere Referenzen:


Diese merkwürdigen Zahlen erscheinen, weil Computer das binäre Zahlensystem (Basis 2) zu Berechnungszwecken verwenden, während wir Dezimalzahlen (Basis 10) verwenden.

Es gibt eine Mehrheit von Bruchzahlen, die weder binär noch dezimal oder beides genau dargestellt werden können. Ergebnis - Eine aufgerundete (aber genaue) Zahl ergibt sich.


Eine andere Frage wurde als Duplikat zu dieser Frage benannt:

Warum unterscheidet sich das Ergebnis in C ++ cout << xvon dem Wert, den ein Debugger anzeigt x?

Das xin der Frage ist eine floatVariable.

Ein Beispiel wäre

float x = 9.9F;

Der Debugger zeigt an 9.89999962, dass die Ausgabe der coutOperation erfolgt 9.9.

Es stellt sich heraus, dass coutdie Standardgenauigkeit für float6 ist, also rundet sie auf 6 Dezimalstellen.

Siehe here für Referenz


Es gibt viele gute Antworten, aber ich möchte noch eine weitere anhängen.

Nicht alle Zahlen können über Floats / Doubles dargestellt werden . Beispielsweise wird die Zahl "0,2" als "0,200000003" in einfacher Genauigkeit im IEEE754-Float-Point-Standard dargestellt.

Modell für das Speichern reeller Zahlen unter der Haube stellen Float-Nummern als dar

Obwohl Sie 0.2leicht tippen können FLT_RADIXund DBL_RADIX2 ist; nicht 10 für einen Computer mit FPU, der den "IEEE-Standard für die binäre Gleitpunktarithmetik (ISO / IEEE Std 754-1985)" verwendet.

Daher ist es ein bisschen schwierig, solche Zahlen genau darzustellen. Auch wenn Sie diese Variable explizit ohne Zwischenberechnung angeben.


Haben Sie die Klebebandlösung ausprobiert?

Versuchen Sie herauszufinden, wann Fehler auftreten, und beheben Sie sie mit kurzen if-Anweisungen. Das ist nicht schön, aber für einige Probleme ist dies die einzige Lösung, und dies ist eine davon.

 if( (n * 0.1) < 100.0 ) { return n * 0.1 - 0.000000000000001 ;}
                    else { return n * 0.1 + 0.000000000000001 ;}    

Ich hatte das gleiche Problem in einem wissenschaftlichen Simulationsprojekt in c #, und ich kann Ihnen sagen, dass, wenn Sie den Schmetterlingseffekt ignorieren, er sich zu einem großen, fetten Drachen wenden wird und Sie in den Arsch beißen **


Mein Workaround:

function add(a, b, precision) {
    var x = Math.pow(10, precision || 2);
    return (Math.round(a * x) + Math.round(b * x)) / x;
}

Genauigkeit bezieht sich auf die Anzahl der Stellen, die Sie während des Hinzufügens nach dem Dezimalzeichen beibehalten möchten.


Nur zum Spaß spielte ich mit der Darstellung von Schwimmern, folgte den Definitionen aus dem Standard C99 und schrieb den Code unten.

Der Code druckt die binäre Darstellung von Floats in 3 getrennten Gruppen

SIGN EXPONENT FRACTION

und danach gibt es eine Summe aus, die, wenn mit ausreichender Genauigkeit summiert wird, den Wert anzeigt, der in der Hardware tatsächlich vorhanden ist.

Wenn Sie also schreiben float x = 999..., wandelt der Compiler diese Zahl in eine von der Funktion gedruckte Bitdarstellung xxso um, dass die von der Funktion gedruckte Summe yyder angegebenen Zahl entspricht.

In Wirklichkeit ist diese Summe nur eine Annäherung. Für die Nummer 999.999.999 fügt der Compiler in Bitdarstellung des Float die Nummer 1.000.000.000 ein

Nach dem Code füge ich eine Konsolensitzung hinzu, in der ich die Summe der Terme für beide Konstanten (minus PI und 999999999) berechnet, die in der Hardware tatsächlich vorhanden sind und vom Compiler dort eingefügt werden.

#include <stdio.h>
#include <limits.h>

void
xx(float *x)
{
    unsigned char i = sizeof(*x)*CHAR_BIT-1;
    do {
        switch (i) {
        case 31:
             printf("sign:");
             break;
        case 30:
             printf("exponent:");
             break;
        case 23:
             printf("fraction:");
             break;

        }
        char b=(*(unsigned long long*)x&((unsigned long long)1<<i))!=0;
        printf("%d ", b);
    } while (i--);
    printf("\n");
}

void
yy(float a)
{
    int sign=!(*(unsigned long long*)&a&((unsigned long long)1<<31));
    int fraction = ((1<<23)-1)&(*(int*)&a);
    int exponent = (255&((*(int*)&a)>>23))-127;

    printf(sign?"positive" " ( 1+":"negative" " ( 1+");
    unsigned int i = 1<<22;
    unsigned int j = 1;
    do {
        char b=(fraction&i)!=0;
        b&&(printf("1/(%d) %c", 1<<j, (fraction&(i-1))?'+':')' ), 0);
    } while (j++, i>>=1);

    printf("*2^%d", exponent);
    printf("\n");
}

void
main()
{
    float x=-3.14;
    float y=999999999;
    printf("%lu\n", sizeof(x));
    xx(&x);
    xx(&y);
    yy(x);
    yy(y);
}

Hier ist eine Konsolensitzung, in der ich den tatsächlichen Wert des in Hardware vorhandenen Floats berechnet. Ich habe bcdie Summe der vom Hauptprogramm ausgegebenen Terme gedruckt. Diese Summe kann man auch in Python reploder ähnliches einfügen .

-- .../terra1/stub
@ qemacs f.c
-- .../terra1/stub
@ gcc f.c
-- .../terra1/stub
@ ./a.out
sign:1 exponent:1 0 0 0 0 0 0 fraction:0 1 0 0 1 0 0 0 1 1 1 1 0 1 0 1 1 1 0 0 0 0 1 1
sign:0 exponent:1 0 0 1 1 1 0 fraction:0 1 1 0 1 1 1 0 0 1 1 0 1 0 1 1 0 0 1 0 1 0 0 0
negative ( 1+1/(2) +1/(16) +1/(256) +1/(512) +1/(1024) +1/(2048) +1/(8192) +1/(32768) +1/(65536) +1/(131072) +1/(4194304) +1/(8388608) )*2^1
positive ( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
-- .../terra1/stub
@ bc
scale=15
( 1+1/(2) +1/(4) +1/(16) +1/(32) +1/(64) +1/(512) +1/(1024) +1/(4096) +1/(16384) +1/(32768) +1/(262144) +1/(1048576) )*2^29
999999999.999999446351872

Das ist es. Der Wert von 999999999 ist tatsächlich

999999999.999999446351872

Sie können auch überprüfen, bcdass -3.14 auch gestört ist. Vergessen Sie nicht, einen scaleFaktor einzustellen bc.

Die angezeigte Summe ist das, was sich in der Hardware befindet. Der Wert, den Sie durch die Berechnung erhalten, hängt von der von Ihnen festgelegten Skala ab. Ich habe den scaleFaktor auf 15 gesetzt. Mathematisch gesehen mit unendlicher Genauigkeit scheint es 1.000.000.000 zu sein.


Eine andere Betrachtungsweise: 64 Bits werden zur Darstellung von Zahlen verwendet. Als Folge gibt es auf keinen Fall mehr als 2 ** 64 = 18.446.744.073.709.551.616 verschiedene Zahlen können genau dargestellt werden.

Laut Math gibt es jedoch unendlich viele Dezimalstellen zwischen 0 und 1. IEE 754 definiert eine Codierung, um diese 64 Bits effizient für einen viel größeren Zahlenraum plus NaN und +/– Infinity zu verwenden, sodass zwischen genau dargestellten Zahlen Lücken bestehen Zahlen nur approximiert.

Leider sitzt 0.3 in einer Lücke.


Gleitkomma-Rundungsfehler. Von Was jeder Informatiker wissen sollten über Gleitpunktarithmetik :

Um unendlich viele reelle Zahlen in eine endliche Anzahl von Bits zu quetschen, ist eine ungefähre Darstellung erforderlich. Obwohl es unendlich viele Ganzzahlen gibt, kann in den meisten Programmen das Ergebnis von Ganzzahlberechnungen in 32 Bit gespeichert werden. Im Gegensatz dazu werden bei einer festen Anzahl von Bits die meisten Berechnungen mit reellen Zahlen Mengen erzeugen, die mit diesen vielen Bits nicht exakt dargestellt werden können. Daher muss das Ergebnis einer Gleitkommaberechnung oft gerundet werden, um wieder in seine endliche Darstellung zu passen. Dieser Rundungsfehler ist das charakteristische Merkmal der Gleitkommaberechnung.


Kann ich nur hinzufügen; Die Leute gehen immer davon aus, dass dies ein Computerproblem ist, aber wenn Sie mit Ihren Händen zählen (Basis 10), können Sie nicht bekommen, es (1/3+1/3=2/3)=truesei denn, Sie haben unendlich, um 0,333 ... bis 0,333 ... hinzuzufügen, also genauso wie bei dem (1/10+2/10)!==3/10Problem in der Basis 2 kürzt man es auf 0,333 + 0,333 = 0,666 und rundet es wahrscheinlich auf 0,667, was auch technisch ungenau wäre.

Zählen Sie dreistufig, und Drittel sind kein Problem - vielleicht würde ein Rennen mit 15 Fingern an jeder Hand fragen, warum Ihre Dezimalmathematik gebrochen wurde ...


Um die beste Lösung anbieten zu können, habe ich folgende Methode entdeckt:

parseFloat((0.1 + 0.2).toFixed(10)) => Will return 0.3

Lassen Sie mich erklären, warum es die beste Lösung ist. Wie bereits in den obigen Antworten erwähnt, ist es ratsam, die toFixed () - Funktion von Javascript zu verwenden, um das Problem zu lösen. Am wahrscheinlichsten werden Sie jedoch auf einige Probleme stoßen.

Stellen Sie sich vor Sie gehen zwei Float - Zahlen addieren , wie 0.2und 0.7hier ist es: 0.2 + 0.7 = 0.8999999999999999.

Ihr erwartetes Ergebnis war 0.9, dass Sie in diesem Fall ein Ergebnis mit 1-stelliger Genauigkeit benötigen. Sie sollten also verwendet haben, (0.2 + 0.7).tofixed(1)aber Sie können nicht einen bestimmten Parameter an toFixed () übergeben, da dieser beispielsweise von der angegebenen Anzahl abhängt

`0.22 + 0.7 = 0.9199999999999999`

In diesem Beispiel benötigen Sie eine 2- toFixed(2)stellige Genauigkeit. Welcher Parameter sollte also für jede gegebene Float-Nummer passen?

Man könnte sagen, dass es in jeder Situation 10 sein sollte:

(0.2 + 0.7).toFixed(10) => Result will be "0.9000000000"

Verdammt! Was machen Sie mit diesen unerwünschten Nullen nach 9? Es ist die Zeit, um es in Float umzuwandeln, um es nach Belieben zu machen:

parseFloat((0.2 + 0.7).toFixed(10)) => Result will be 0.9

Nachdem Sie die Lösung gefunden haben, ist es besser, sie als eine Funktion wie diese anzubieten:

function floatify(number){
           return parseFloat((number).toFixed(10));
        }

Probieren wir es selbst aus:

function floatify(number){
       return parseFloat((number).toFixed(10));
    }
 
function addUp(){
  var number1 = +$("#number1").val();
  var number2 = +$("#number2").val();
  var unexpectedResult = number1 + number2;
  var expectedResult = floatify(number1 + number2);
  $("#unexpectedResult").text(unexpectedResult);
  $("#expectedResult").text(expectedResult);
}
addUp();
input{
  width: 50px;
}
#expectedResult{
color: green;
}
#unexpectedResult{
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="number1" value="0.2" onclick="addUp()" onkeyup="addUp()"/> +
<input id="number2" value="0.7" onclick="addUp()" onkeyup="addUp()"/> =
<p>Expected Result: <span id="expectedResult"></span></p>
<p>Unexpected Result: <span id="unexpectedResult"></span></p>

Sie können es folgendermaßen verwenden:

var x = 0.2 + 0.7;
floatify(x);  => Result: 0.9

Da W3SCHOOLS vorschlägt, dass es auch eine andere Lösung gibt, können Sie multiplizieren und dividieren, um das obige Problem zu lösen:

var x = (0.2 * 10 + 0.1 * 10) / 10;       // x will be 0.3

Denken Sie daran, dass dies (0.2 + 0.1) * 10 / 10nicht funktionieren wird, obwohl es das Gleiche scheint! Ich bevorzuge die erste Lösung, da ich sie als Funktion anwenden kann, die den Eingabeflosser in einen genauen Ausgabeflosser konvertiert.





floating-accuracy