2009-04-15 16 views
12

Perché il risultato di questo cast esplicito è diverso da quello implicito?Perché il risultato di questo cast esplicito è diverso da quello implicito?

#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 
    } 

Se faccio d = (lunga) ((b - a + c)/c); Ho anche 10 anni. Perché l'assegnazione di un doppio fa la differenza?

+0

sono gli stessi (entrambi 11) sul mio sistema? –

+0

con cosa stai compilando questo? – Joseph

+0

Solo per divertimento, prova a fare una variabile locale e vedere se questo cambia le cose. –

risposta

16

io sospetto la differenza è una conversione da un valore a virgola mobile 80-bit per una lunga vs una conversione da un valore a virgola mobile 80 bit a 64 bit uno e poi una conversione per un lungo.

(La ragione di 80 bit in arrivo a tutti è che questo è un tipico precisione utilizzato per aritmetica effettivo, e la larghezza dei registri in virgola mobile.)

Supponiamo il risultato di 80 bit è qualcosa di simile 10,999999999999999 - la conversione da quella ad una lunga resa 10. Tuttavia, il valore a virgola mobile a 64 bit più vicino al valore di 80-bit è in realtà 11.0, quindi la conversione a due stadi termina producendo 11.

EDIT: Per dare a questo un un po 'più di peso ...

Ecco un programma Java che utilizza l'aritmetica di precisione arbitraria per eseguire il sam e calcolo. Si noti che converte il doppio valore più vicino a 0,1 in un BigDecimal - quel valore è 0.1000000000000000055511151231257827021181583404541015625. (In altre parole, il risultato esatto del calcolo è non 11 comunque.)

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); 
    } 
} 

Ecco il risultato:

10.9999999999999994448884876874217606030632 

In altre parole, questo è corretto per circa 40 cifre decimali (modo può gestire più di 64 o 80 bit in virgola mobile).

Ora, prendiamo in considerazione l'aspetto di questo numero in binario. Non ho strumenti per fare facilmente la conversione, ma ancora una volta possiamo usare Java per aiutare. Supponendo un numero normalizzato, la parte "10" finisce usando tre bit (uno in meno rispetto a undici = 1011). Ciò lascia 60 bit di mantissa per una precisione estesa (80 bit) e 48 bit per una precisione doppia (64 bit).

Quindi, qual è il numero più vicino a 11 in ciascuna precisione? Anche in questo caso, usiamo Java:

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)));   
    } 
} 

Risultati:

10.999999999999999999132638262011596452794037759304046630859375 
10.999999999999996447286321199499070644378662109375 

Così, i tre numeri che abbiamo sono:

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

Ora calcolare il valore più vicino a quella corretta per ogni precisione - per una precisione estesa, è inferiore a 11. Arrotondare ciascuno di questi valori su un valore lungo, e si finisce con 10 e 11 rispettivamente.

Speriamo che questo sia abbastanza prove per convincere i dubbiosi;)

+0

È un'ipotesi plausibile aver visto effetti simili in C#. Sarà btw dipendente dal processore e dal compilatore. Sono sicuro al 100% di ciò che sta succedendo? No. Penso che sia una spiegazione molto probabile? Assolutamente. Più utile di "funziona sulla mia macchina" IMO. –

+0

http://babbage.cs.qc.edu/IEEE-754/ è molto utile per questo genere di cose, sebbene abbia solo calcolatrici a 32 e 64 bit, non una calcolatrice a 80 bit. –

+0

@Adam: Grazie mille per il collegamento. Utile davvero. Sarebbe utile se il valore finale "decimale" fosse il valore * esatto * rappresentato dal doppio più vicino però. –

0

diritta copia/incolla e compilare su Linux mi dà 11 per entrambi. L'aggiunta di d = (long) ((b - a + c)/c); dà anche 11. Lo stesso vale per OpenBSD.

+0

. Compilatore + opzioni + processore sono molto più rilevanti. –

1

codepad.org (gcc 4.1.2) inverte i risultati del tuo esempio, mentre sul mio sistema locale (gcc 4.3.2) ottengo 11 in entrambi i casi. Questo mi suggerisce che si tratta di un problema in virgola mobile. In alternativa, potrebbe teoricamente troncare (b - a + c) che, in un contesto intero, valuterà a (2 - 1 + 0)/.1, che sarebbe 10, mentre in un contesto float (2.0 - 1.0 + 0.1)/.1 = 1.1/.1 = 11. Sarebbe strano però.

+0

Il valore di c non è 0,1 per iniziare. È il doppio più vicino a 0,1. –

2

Ottengo 10 & 11 sul mio sistema x86 x86 a 32 bit con gcc 4.3.2.

Il relativo C/ASM è qui:

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 

La risposta è lasciata come esercizio per il lettore interessato.

Problemi correlati