Frage

Betrachten Sie den folgenden C # -Code:

double result1 = 1.0 + 1.1 + 1.2;
double result2 = 1.2 + 1.0 + 1.1;

if (result1 == result2)
{
    ...
}

result1 sollte immer gleich result2 richtig? Die Sache ist, es funktioniert nicht. result1 ist 3.3 und result2 ist 3,3000000000000003. Der einzige Unterschied ist die Reihenfolge der Konstanten.

Ich weiß, dass verdoppelt so umgesetzt werden, dass Rundungsprobleme auftreten können. Ich bin mir bewusst, dass ich Dezimalzahlen stattdessen verwenden kann, wenn ich absolute Präzision benötigen. Oder das kann ich Math.Round () in meiner if-Anweisung verwenden. Ich bin nur ein Nerd, der will, verstehen, was der C # -Compiler tut. Kann mir jemand sagen?

Edit:

Danke an alle, die bisher vorgeschlagenen auf Gleitkomma-Arithmetik zu lesen und / oder sprachen über die inhärente Ungenauigkeit, wie die CPU übernimmt verdoppelt. Aber ich fühle mich die Hauptstoßrichtung meiner Frage noch unbeantwortet ist. Das ist meine Schuld für nicht richtig formulieren. Lassen Sie es mich so sagen:

Sie den obigen Code Breaking down, würde ich die folgenden Operationen erwarten zu geschehen:

double r1 = 1.1 + 1.2;
double r2 = 1.0 + r1
double r3 = 1.0 + 1.1
double r4 = 1.2 + r3

Nehmen wir an, dass jeder der oben genannten Ergänzungen hatten einen Rundungsfehler (nummeriert e1..e4). So enthält r1 Fehler e1 Rundungs, r2 Rundungsfehler e1 + e2 enthält, r3 enthält e3 und r4 enthält e3 + e4.

Nun, ich weiß nicht, wie genau, wie die Rundungsfehler passieren, aber ich würde e1 + e2 gleich e3 + e4 erwartet. Natürlich ist es nicht, aber das scheint irgendwie falsch zu mir. Ein andere Sache ist, dass wenn ich den obigen Code ausführen, ich habe keinen Rundungsfehler erhalten. Das macht ich denke, es ist der C # -Compiler, die etwas seltsam und nicht die CPU tut.

Ich weiß, ich bin viel zu fragen und vielleicht der beste Antwort kann jeder ist gibt einen PHD in CPU-Design zu gehen und zu tun, aber ich dachte, ich würde fragen.

Edit 2

Mit Blick auf die IL von meiner ursprünglichen Codebeispiel, ist es klar, dass es der Compiler nicht die CPU, die dies tut:

.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
    .maxstack 1
    .locals init (
        [0] float64 result1,
        [1] float64 result2)
    L_0000: nop 
    L_0001: ldc.r8 3.3
    L_000a: stloc.0 
    L_000b: ldc.r8 3.3000000000000003
    L_0014: stloc.1 
    L_0015: ret 
}

Der Compiler fügt die Zahlen für mich!

War es hilfreich?

Lösung

  

Ich hätte e1 + e2 gleich e3 + e4 erwartet.

Das ist nicht ganz anders als erwartet

 floor( 5/3 ) + floor( 2/3 + 1 )

gleich

 floor( 5/3 + 2/3 ) + floor( 1 )

außer Sie von 2 ^ 53 multipliziert wird, bevor der Boden unter.

Mit 12-Bit-precision floating point und Abschneiden mit Ihren Werten:

1.0            =  1.00000000000
1.1            =  1.00011001100
1.2            =  1.00110011001

1.0 + 1.1      = 10.00011001100 // extended during sum
r1 = 1.0 + 1.1 = 10.0001100110  // truncated to 12 bit
r1  + 1.2      = 11.01001100101 // extended during sum
r2 = r1  + 1.2 = 11.0100110010  // truncated to 12 bit

1.1 + 1.2      = 10.01001100110 // extended during sum
r3 = 1.1 + 1.2 = 10.0100110011  // truncated to 12 bit
r3 + 1.0       = 11.01001100110 // extended during sum
r4 = r3  + 1.0 = 11.0100110011  // truncated to 12 bit

So ändert die Reihenfolge der Operationen / Verkürzungen bewirkt, dass der der Fehler ändern, und r4! = R2. Wenn Sie 1.1 und 1.2 in diesem System hinzufügen, trägt das letzte Bit, so nicht auf Verkürzungs verloren. Wenn Sie 1,0 bis 1,1 hinzufügen, wird das letzte Bit von 1,1 verloren und so das Ergebnis ist nicht das gleiche.

Bei einer Bestellung, die Rundung (durch Abschneiden) entfernt einen nachgestellten 1.

In der anderen Anordnung, die Rundung entfernt ein nachlauf 0 beide Male.

Eine nicht gleich Null ist; so die Fehler sind nicht das gleiche.

Doubles haben viel mehr Bits an Präzision und C # wahrscheinlich verwendet Runden statt Abschneiden, aber hoffentlich dieses einfache Modell zeigen Sie verschiedene Fehler mit unterschiedlichen Anordnungen der gleichen Werte passieren können.

Der Unterschied zwischen fp und Mathematik ist, dass + Abkürzung für ‚fügen Sie dann Runde‘ ist nicht nur hinzuzufügen.

Andere Tipps

Der C # -Compiler ist nichts zu tun. Die CPU ist.

Wenn Sie ein in einem CPU-Register, und Sie dann B hinzufügen, ist das Ergebnis in diesem Register gespeichert ist A + B, auf die schwebende Genauigkeit angenähert verwendet

Wenn Sie dann C hinzufügen, fügt der Fehler auf. Dieser Fehler hinaus ist keine transitive Operation, also die letzte Differenz.

Siehe klassischen Papier (Was jeder Informatiker über Gleitkommazahlen wissen sollten Arithmetik) zu diesem Thema. Diese Art von Material ist, was mit Gleitkomma-Arithmetik geschieht. Es dauert einen Informatiker, Ihnen zu sagen, dass 03.01 + 03.01 + 03.01 is'nt gleich 1 ...

Order of Gleitkommaoperationen ist wichtig. Nicht direkt Ihre Frage beantworten, aber Sie sollten immer vorsichtig zu vergleichen Gleitkommazahlen sein. Es ist üblich, eine Toleranz enthalten:

double epsilon = 0.0000001;
if (abs(result1 - result2) <= epsilon)
{
    ...
}

Dies kann interessant sein: Was jeder Informatiker über Floating- wissen sollten Punktarithmetik

  

result1 sollte immer gleich result2   nicht wahr?

Wrong . Das stimmt in der Mathematik, aber nicht in Floating-Point-Arithmetik .

Sie werden einige Numerische Analysis Primer lesen müssen.

Warum die Fehler nicht gleich ist je nach Auftrag kann mit einem anderen Beispiel erläutert.

Lassen Sie uns sagen, dass für Zahlen unter 10, sie alle Zahlen speichern kann, so kann sie speichern 1, 2, 3, und so weiter bis einschließlich 10, aber nach 10, kann es nur jede zweite Zahl speichern, aufgrund mit anderen Worten zu internem Genauigkeitsverlust, kann es nur dann gespeichert, 10, 12, 14, etc.

Jetzt, mit diesem Beispiel sehen Sie, warum die folgende unterschiedliche Ergebnisse:

1 + 1 + 1 + 10 = 12 (or 14, depending on rounding)
10 + 1 + 1 + 1 = 10

Das Problem mit Gleitkommazahlen ist, dass sie nicht genau dargestellt werden können, und der Fehler geht nicht immer auf die gleiche Weise, so wird die Reihenfolge wichtig.

Zum Beispiel 3,00000000003 + 3,00000000003 könnte am Sein 6,00000000005 (Mitteilung nicht 6 am Ende) enden, aber 3,00000000003 + 2,99999999997 könnte am Ende 6,00000000001 zu sein, und mit, dass:

step 1: 3.00000000003 + 3.00000000003 = 6.00000000005
step 2: 6.00000000005 + 2.99999999997 = 9.00000000002

, aber die Reihenfolge ändern:

step 1: 3.00000000003 + 2.99999999997 = 6.00000000001
step 2: 6.00000000001 + 3.00000000003 = 9.00000000004

So wird es egal.

Nun, natürlich, könnten Sie, dass die obigen Beispiele balancieren sich gegenseitig aus, dass die erste schwingt nach oben durch .xxx1 und die andere nach unten durch .xxx1 Glück, so dass Sie in beide .xxx3, aber es gibt keine Garantie.

Sie verwenden eigentlich nicht die gleichen Werte, weil die Zwischenergebnisse sind unterschiedlich:

double result1 = 2.1 + 1.2;
double result2 = 2.2 + 1.1;

Da verdoppelt werden nicht Dezimalzahlen darstellen kann genau erhalten Sie unterschiedliche Ergebnisse.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top