2014-11-07 7 views
7

Utilizzando g ++ 4.9.2 se compilosenza segno a 64 bit a doppia conversione: il motivo per cui questo algoritmo da g ++

bool int_dbl_com(const unsigned long long x, const double y) 
{ 
    return x <= y; 
} 

allora l'uscita assembler è:

testq  %rcx, %rcx 
js  .L2 
pxor  %xmm0, %xmm0 
cvtsi2sdq %rcx, %xmm0 
ucomisd %xmm0, %xmm1 
setae  %al 
ret 

Il comando cvtsi2sdq è firmato Conversione e la prima combinazione di test e salto è per verificare se %rcx < 0. Se è così, andiamo a L2, e questo non capisco:

.L2: 
movq  %rcx, %rax 
andl  $1, %ecx 
pxor  %xmm0, %xmm0 
shrq  %rax 
orq  %rcx, %rax 
cvtsi2sdq %rax, %xmm0 
addsd  %xmm0, %xmm0 
ucomisd %xmm0, %xmm1 
setae  %al 
ret 

Ingenuamente, si potrebbe dimezzare %rcx, convertire in doppio in %xmm0, e quindi aggiungere %xmm0 a se stesso per tornare al valore originale (accettando, ovviamente, di aver perso una certa precisione di ordine basso passando da un intero a 64 bit a un float a 64 bit).

Ma questo non è ciò che fa il codice: sembra salvare il bit di ordine più basso di %rcx e restituirlo al risultato. Perché?? E perché preoccuparsi quando questi bit di basso ordine andranno persi lo stesso (o sbaglio qui)?

(Lo stesso algoritmo sembra essere utilizzato a prescindere dalla ottimizzazione; ho usato -O3 qui per rendere più facile da vedere.)

risposta

12
.L2: 
movq  %rcx, %rax 
andl  $1, %ecx  ; save the least significant bit of %rax 
pxor  %xmm0, %xmm0 
shrq  %rax   ; make %rax represent half the original number, as a signed value 
orq  %rcx, %rax  ; “round to odd”: if the division by two above was not exact, ensure the result is odd 
cvtsi2sdq %rax, %xmm0 ; convert to floating-point 
addsd  %xmm0, %xmm0 ; multiply by two 
ucomisd %xmm0, %xmm1 ; compare … 
setae  %al 
ret 

Gli ultimi tre istruzioni implementano <= e return dal codice sorgente. Gli altri fanno tutti parte della conversione da uint64_t a double.

Il passaggio difficile da comprendere è quello che ho commentato come "round to odd". "Arrotondare a dispari" è una tecnica che previene i cattivi effetti di “double rounding”.

In effetti, l'algoritmo deve convertire da 64 bit a 63 bit e quindi da 63 bit a IEEE 754 binario64. Se implementate in modo ingenuo, in alcuni casi, queste due conversioni possono produrre un risultato che differisce da una singola conversione diretta dal numero intero a 64 bit a virgola mobile. Questo è ciò che viene chiamato "doppio arrotondamento".

Rounding to odd ensures che il risultato dell'arrotondamento intermedio non è un valore che verrebbe arrotondato nella direzione errata in caso di arrotondamento doppio. Questo è sufficiente per rendere le sequenze sotto equivalente per tutti gli ingressi:

64-bit ---(round to odd)---> 63-bit ---(round to nearest even)----> binary64 
64-bit -(round-to-nearest-even,the conversion the compiler wants)-> binary64 

Per rispondere a altri aspetti della vostra domanda:

Ma questo non è quello che fa il codice: sembra per salvare il più basso-ordine bit di %rcx e quindi restituisce il risultato. Perché?? E perché preoccuparsi quando questi bit di basso ordine andranno persi lo stesso (o sbaglio qui)?

Questo è esattamente come implementare round-to-odd in questa particolare istanza. Il bit meno significativo di %rcx è uno se il cambiamento non è una divisione esatta per due, e in questo caso, il risultato deve essere reso dispari.

Lo stesso algoritmo sembra essere utilizzato indipendentemente dall'ottimizzazione; Ho usato -O3 qui per renderlo più facile da vedere.

L'istruzione sequenza è ottimale (per quanto posso vedere, per processori moderni) e corrisponde alla conversione a livello sorgente da uint64_t int a double. Non richiede alcuno sforzo da parte del compilatore per utilizzarlo anche al livello di ottimizzazione più basso. Ciò che potrebbe accadere con l'ottimizzazione (ma non succede qui) è che le istruzioni sono combinate con altre istruzioni che corrispondono ad altri costrutti di livello sorgente. Ma non ha senso avere una sequenza di istruzioni diversa da quella ottimale da generare per le conversioni allo -O0.

+0

Grande! Voglio tirare fuori il link che hai dato come una grande spiegazione di _why_ round to odd: http://www.exploringbinary.com/gcc-avoids-double-rounding-errors-with-round-to-odd/ –

+0

@MatthewDaws L'intero blog "Exploring Binary" è fantastico. :) –