2015-07-14 9 views
9

So tutto sui problemi di approssimazione con i numeri in virgola mobile, quindi capisco come 4,5 può essere arrotondato a 4 se è stato approssimato come 4.4999999999999991. La mia domanda è perché c'è una differenza con gli stessi tipi con 32 bit e 64 bit.Differenze in virgola mobile tra 64 bit e 32 bit con Round

Nel codice seguente ho due calcoli. A 32 bit il valore di MyRoundValue1 è 4 e il valore di MyRoundValue2 è 5. A 64 bit sono entrambi 4. I risultati non dovrebbero essere coerenti sia con 32 bit sia con 64 bit?

{$APPTYPE CONSOLE} 
const 
    MYVALUE1: Double = 4.5; 
    MYVALUE2: Double = 5; 
    MyCalc: Double = 0.9; 
var 
    MyRoundValue1: Integer; 
    MyRoundValue2: Integer; 
begin 
    MyRoundValue1 := Round(MYVALUE1); 
    MyRoundValue2 := Round(MYVALUE2 * MyCalc); 
    WriteLn(IntToStr(MyRoundValue1)); 
    WriteLn(IntToStr(MyRoundValue2)); 
end. 

risposta

7

In x87 di questo codice:

MyRoundValue2 := Round(MYVALUE2 * MyCalc); 

viene compilato a:

 
MyRoundValue2 := Round(MYVALUE2 * MyCalc); 
0041C4B2 DD0508E64100  fld qword ptr [$0041e608] 
0041C4B8 DC0D10E64100  fmul qword ptr [$0041e610] 
0041C4BE E8097DFEFF  call @ROUND 
0041C4C3 A3C03E4200  mov [$00423ec0],eax 

La parola di controllo di default per l'unità x87 sotto la Delphi RTL esegue i calcoli a 80 bit di precisione. Quindi l'unità di virgola mobile moltiplica 5 dal closest 64 bit value to 0.9 che è:

 
0.90000 00000 00000 02220 44604 92503 13080 84726 33361 81640 625 

noti che questo valore è maggiore di 0,9. E si scopre che quando moltiplicato per 5 e arrotondato al valore di 80 bit più vicino, il valore è maggiore di 4,5. Quindi Round(MYVALUE2 * MyCalc) restituisce 5.

Su 64 bit, la matematica in virgola mobile viene eseguita sull'unità SSE. Questo non usa valori intermedi a 80 bit. E si scopre che 5 volte il doppio più vicino a 0,9, arrotondato alla doppia precisione è esattamente 4,5. Quindi, Round(MYVALUE2 * MyCalc) restituisce 4 in 64 bit.

È possibile convincere il compilatore a 32 bit a comportarsi nello stesso modo come il compilatore a 64 bit per l'archiviazione ad un doppio piuttosto che basarsi su valori 80 bit intermedi:

{$APPTYPE CONSOLE} 
const 
    MYVALUE1: Double = 4.5; 
    MYVALUE2: Double = 5; 
    MyCalc: Double = 0.9; 
var 
    MyRoundValue1: Integer; 
    MyRoundValue2: Integer; 
    d: Double; 
begin 
    MyRoundValue1 := Round(MYVALUE1); 
    d := MYVALUE2 * MyCalc; 
    MyRoundValue2 := Round(d); 
    WriteLn(MyRoundValue1); 
    WriteLn(MyRoundValue2); 
end. 

Questo programma produce lo stesso output tua Programma a 64 bit.

Oppure è possibile forzare l'unità x87 a utilizzare intermedi a 64 bit.

{$APPTYPE CONSOLE} 
uses 
    SysUtils; 
const 
    MYVALUE1: Double = 4.5; 
    MYVALUE2: Double = 5; 
    MyCalc: Double = 0.9; 
var 
    MyRoundValue1: Integer; 
    MyRoundValue2: Integer; 
begin 
    Set8087CW($1232); // <-- round intermediates to 64 bit 
    MyRoundValue1 := Round(MYVALUE1); 
    MyRoundValue2 := Round(MYVALUE2 * MyCalc); 
    WriteLn(MyRoundValue1); 
    WriteLn(MyRoundValue2); 
end. 
+0

Non oserei cambiare la parola di comando fpu. –

+1

@LURD Oserei. Ci sono molti scenari in cui devi. Un buon esempio è quando si tratta di librerie esterne. A volte non gli piace se le eccezioni vengono smascherate. Ti sto guardando in Excel 2013. Nel mio lavoro, ottenere la versione a 32 bit per comportarsi vicino alla versione a 64 bit è importante. Quindi '$ 1232' è come rotola la mia versione a 32 bit. –

+2

@LURD Naturalmente, come tutti voi dovete essere stufo di me dicendo, non aiuta che la funzione RTL Delphi Set8087CW non sia protetta da thread. Come ho detto tante volte, ho detto a Emba come risolvere questo problema, ma non lo faranno. Forse perché sono troppo spaventati per cambiare. –

3

System.Round internamente accetta un Extended valore . Nei calcoli a 32 bit vengono effettuati come Extended all'interno della FPU. In 64-bit Esteso è simile a Double. La rappresentazione interna potrebbe differire così tanto per fare la differenza.

+0

'Extended' non è * simile a Double * in 64 bit, è * IS * a' Double'. 'Extended' in 32 bit è un tipo di dati FPU 80bit nativo, ma in 64 bit è solo un alias per' Double'. Sono 16 bit di precisione persa nei sistemi a 64 bit. Questo è [documentato] (http://docwiki.embarcadero.com/Libraries/XE8/en/System.Extended): "Sui sistemi Win32, la dimensione di System.Extended è 10 byte. Sui sistemi Win64, tuttavia, il * * Il tipo System.Extended ** è un alias per System.Double, che è solo 8 byte. Questa differenza può influire negativamente sulla precisione numerica nelle operazioni in virgola mobile. " –

Problemi correlati