Domanda

Perché il risultato di questo cast esplicito diversa da quella implicita?

#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. Perché l'assegnazione ad un doppio fare la differenza?

È stato utile?

Soluzione

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 un lungo.

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

Supponiamo che il risultato di 80 bit è qualcosa di simile 10,999999999999999 - la conversione da quello a una lunga rendimenti 10. Tuttavia, il valore di virgola mobile 64 bit più vicino al valore di 80 bit sia effettivamente 11.0, quindi la conversione a due stadi finisce cedevole 11.

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

Ecco un programma Java che utilizza l'aritmetica precisione arbitraria di fare lo stesso calcolo. Si noti che converte il doppio valore più vicino a 0,1 in un BigDecimal - che il 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, è corretto a circa 40 cifre decimali (modo più di 64 o 80 bit floating point supporta).

Ora, prendiamo in considerazione ciò che questo numero sembra in binario. Non ho alcun attrezzo per fare facilmente la conversione, ma ancora una volta possiamo usare Java per aiutarvi. Supponendo un numero normalizzato, la parte "10" finisce usando tre bit (uno in meno per undici = 1011). Questo lascia 60 bit della mantissa di precisione estesa (80 bit) e 48 bit per doppia precisione (64 bit).

Quindi, qual è il numero più vicino a 11 in ciascuna di 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

Quindi, 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 la precisione estesa, è meno di 11. Turno ciascuno di questi valori a una lunga, e si finisce con, rispettivamente, 10 e 11

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

Altri suggerimenti

ho 10 e 11 sul mio 32 bit sistema x86 Linux in esecuzione gcc 4.3.2, anche.

La rilevante 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.

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 fa pensare che si tratta di un problema in virgola mobile. In alternativa, potrebbe essere teoricamente troncamento (b - a + c) che, in un contesto intero sarebbe valutato come (2 - 1 + 0) / .1, che sarebbe 10, mentre in un contesto galleggiante (2.0 - 1.0 + 0.1 ) / 0,1 = 1,1 / 0,1 = 11. Questo sarebbe strano però.

Diritto 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 su OpenBSD.

Ecco un mucchio di dettagli sulle questioni in virgola mobile e un ottimo articolo . Ma in fondo, non tutti i valori in virgola mobile possono essere rappresentati da un certo numero di bit (32 bit o 64 bit o altro). Questo è un tema profondo, ma mi piace perché mi ricorda di Prof. Kahan . :)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top