2009-04-15 7 views
12

Warum unterscheidet sich das Ergebnis dieser expliziten Umwandlung von der impliziten?Warum unterscheidet sich das Ergebnis dieser expliziten Umwandlung von der impliziten?

#include <stdio.h> 

double a; 
double b; 
double c; 

long d; 

double e; 

int main() { 
    a = 1.0; 
    b = 2.0; 
    c = .1; 

    d = (b - a + c)/c; 
    printf("%li\n", d);  // 10 

    e = (b - a + c)/c; 
    d = (long) e; 
    printf("%li\n", d);  // 11 
    } 

Wenn ich d = (lang) ((b - a + c)/c); Ich bekomme auch 10. Warum macht die Zuordnung zu einem Doppel einen Unterschied?

+0

sie sind die gleichen (beide 11) auf meinem System? –

+0

mit was kompilierst du das? – Joseph

+0

Nur zum Spaß, versuchen Sie, eine lokale Variable zu machen und sehen Sie, ob das die Dinge ändert. –

Antwort

16

Ich nehme an dem Unterschied ist eine Umstellung von einem 80-Bit-Gleitkomma-Wert auf einen lang vs einer Umwandlung von einem 80-Bit-Gleitkomma-Wert in eine 64-Bit und eine dann eine Umwandlung in eine lang.

(Der Grund für die 80 Bits überhaupt kommen, ist, dass, dass eine typische Genauigkeit ist für die eigentliche Arithmetik verwendet, und die Breite der Gleitkommaregister.)

das Ergebnis 80-Bit Angenommen, so etwas wie 10,999999999999999 ist - die Umwandlung von diesem zu einer langen Renditen 10. Jedoch ist der nächste 64-Bit-Gleitkommawert zu dem 80-Bit-Wert tatsächlich 11,0, so endet die zweistufige Umwandlung bis ergibt 11.

EDIT: Um dies zu geben a etwas mehr Gewicht ...

Hier ist ein Java-Programm, das Arithmetik mit beliebiger Genauigkeit verwendet, um das zu tun die Berechnung. Beachten Sie, dass der Doppelwert, der 0,1 am nächsten liegt, in BigDecimal konvertiert wird. Dieser Wert ist 0,10000000000000000555511151231257827021181583404541015625. (Mit anderen Worten, das genaue Ergebnis der Berechnung ist nicht 11 sowieso.)

import java.math.*; 

public class Test 
{ 
    public static void main(String[] args) 
    { 
     BigDecimal c = new BigDecimal(0.1d);   
     BigDecimal a = new BigDecimal(1d); 
     BigDecimal b = new BigDecimal(2d); 

     BigDecimal result = b.subtract(a) 
          .add(c) 
          .divide(c, 40, RoundingMode.FLOOR); 
     System.out.println(result); 
    } 
} 

Hier ist das Ergebnis:

10.9999999999999994448884876874217606030632 

Mit anderen Worten, dass bis etwa 40 Dezimalstellen korrekt ist (Art und Weise mehr als entweder 64 oder 80 Bit Fließkomma verarbeiten kann).

Betrachten wir nun, wie diese Zahl im Binärformat aussieht. Ich habe keine Werkzeuge, um die Konvertierung leicht durchzuführen, aber wieder können wir Java verwenden, um zu helfen. Unter der Annahme einer normalisierten Zahl endet der Teil "10" mit drei Bits (eine weniger als für elf = 1011). Das hinterlässt 60 Bits Mantisse für erweiterte Präzision (80 Bits) und 48 Bits für doppelte Präzision (64 Bits).

Also, was ist die nächste Nummer 11 in jeder Genauigkeit? Auch hier wollen wir Java verwenden:

import java.math.*; 

public class Test 
{ 
    public static void main(String[] args) 
    { 
     BigDecimal half = new BigDecimal("0.5");   
     BigDecimal eleven = new BigDecimal(11); 

     System.out.println(eleven.subtract(half.pow(60))); 
     System.out.println(eleven.subtract(half.pow(48)));   
    } 
} 

Ergebnisse: So

10.999999999999999999132638262011596452794037759304046630859375 
10.999999999999996447286321199499070644378662109375 

, die drei Zahlen, die wir haben, sind:

Correct value: 10.999999999999999444888487687421760603063... 
11-2^(-60): 10.999999999999999999132638262011596452794037759304046630859375 
11-2^(-48): 10.999999999999996447286321199499070644378662109375 

nun den nächsten Wert auf die richtige trainieren für jede Genauigkeit - für erweiterte Präzision ist es weniger als 11. Runden Sie jeden dieser Werte zu einem langen, und Sie am Ende mit 10 und 11 jeweils.

Hoffentlich ist dies genug Beweise, die Zweifler zu überzeugen;)

+0

Es ist eine fundierte Vermutung, ähnliche Effekte in C# gesehen zu haben. Es wird Prozessor und Compiler abhängig sein. Bin ich 100% sicher, dass das passiert? Nein. Glaub ich, es ist eine sehr wahrscheinliche Erklärung? Absolut. Nützlicher als "funktioniert auf meiner Maschine" IMO. –

+0

http://babbage.cs.qc.edu/IEEE-754/ ist sehr hilfreich für diese Art von Sache, obwohl es nur 32- und 64-Bit-Rechner hat, nicht ein 80-Bit-Rechner. –

+0

@Adam: Vielen Dank für den Link. Nützlich in der Tat. Es wäre nützlich, wenn der letzte "Dezimal" -Wert der * genaue * Wert wäre, der durch den nächstliegenden Doppelwert dargestellt wird. –

0

Gerade copy/paste und kompilieren unter Linux gibt mir 11 für beide. Hinzufügen von d = (long) ((b - a + c)/c); ergibt auch 11. Gleiches gilt für OpenBSD.

+0

OS ist wahrscheinlich egal. Compiler + Optionen + Prozessor sind weitaus relevanter. –

1

codepad.org (gcc 4.1.2) kehrt die Ergebnisse Ihres Beispiels um, während ich auf meinem lokalen System (gcc 4.3.2) in beiden Fällen 11 bekomme. Dies deutet darauf hin, dass es sich um ein Gleitkomma-Problem handelt. Alternativ könnte es theoretisch abgeschnitten werden (b - a + c), was in einem Ganzzahlkontext zu (2 - 1 + 0)/0,1, was 10 wäre, während in einem Gleitkontext (2,0 - 1,0 + 0,1)/.1 = 1.1/.1 = 11. Das wäre aber seltsam.

+0

Der Wert von c ist nicht gleich 0,1. Es ist nur das nächste Doppelte zu 0.1. –

2

Ich bekomme 10 & 11 auf meinem 32-Bit-x86-Linux-System läuft gcc 4.3.2, auch.

Die entsprechende C/asm ist hier:

26:foo.c   ****  d = (b - a + c)/c;            
    42       .loc 1 26 0 
    43 0031 DD050000    fldl b 
    43  0000 
    44 0037 DD050000    fldl a 
    44  0000 
    45 003d DEE9     fsubrp %st, %st(1) 
    46 003f DD050000    fldl c 
    46  0000 
    47 0045 DEC1     faddp %st, %st(1) 
    48 0047 DD050000    fldl c 
    48  0000 
    49 004d DEF9     fdivrp %st, %st(1) 
    50 004f D97DFA    fnstcw -6(%ebp) 
    51 0052 0FB745FA    movzwl -6(%ebp), %eax 
    52 0056 B40C     movb $12, %ah 
    53 0058 668945F8    movw %ax, -8(%ebp) 
    54 005c D96DF8    fldcw -8(%ebp) 
    55 005f DB5DF4    fistpl -12(%ebp) 
    56 0062 D96DFA    fldcw -6(%ebp) 
    57 0065 8B45F4    movl -12(%ebp), %eax 
    58 0068 A3000000    movl %eax, d 
    58  00 
    27:foo.c   **** 
    28:foo.c   ****  printf("%li\n", d);             
    59       .loc 1 28 0 
    60 006d A1000000    movl d, %eax 
    60  00 
    61 0072 89442404    movl %eax, 4(%esp) 
    62 0076 C7042400    movl $.LC3, (%esp) 
    62  000000 
    63 007d E8FCFFFF    call printf 
    63  FF 
    29:foo.c   ****  // 10               
    30:foo.c   **** 
    31:foo.c   ****  e = (b - a + c)/c;            
    64       .loc 1 31 0 
    65 0082 DD050000    fldl b 
    65  0000 
    66 0088 DD050000    fldl a 
    66  0000 
    67 008e DEE9     fsubrp %st, %st(1) 
    68 0090 DD050000    fldl c 
    68  0000 
    69 0096 DEC1     faddp %st, %st(1) 
    70 0098 DD050000    fldl c 
    70  0000 
    71 009e DEF9     fdivrp %st, %st(1) 
    72 00a0 DD1D0000    fstpl e 
    72  0000 
    32:foo.c   **** 
    33:foo.c   ****  d = (long) e;              
    73       .loc 1 33 0 
    74 00a6 DD050000    fldl e 
    74  0000 
    75 00ac D97DFA    fnstcw -6(%ebp) 
    76 00af 0FB745FA    movzwl -6(%ebp), %eax 
    77 00b3 B40C     movb $12, %ah 
    78 00b5 668945F8    movw %ax, -8(%ebp) 
    79 00b9 D96DF8    fldcw -8(%ebp) 
    80 00bc DB5DF4    fistpl -12(%ebp) 
    81 00bf D96DFA    fldcw -6(%ebp) 
    82 00c2 8B45F4    movl -12(%ebp), %eax 
    83 00c5 A3000000    movl %eax, d 
    83  00 

Die Antwort als eine Übung gelassen für den interessierten Leser.