2012-01-19 14 views
8

In un'app ad alte prestazioni troviamo che la CPU può calcolare l'aritmetica lunga in modo significativamente più veloce rispetto a doppio. Tuttavia, nel nostro sistema è stato determinato che non abbiamo mai bisogno di più di 9 posizioni decimali di precisione. Quindi usiamo i long per tutte le aritmetiche in virgola mobile con una precisione di 9 punti capita.Converti mantissa ed esponente in doppio

Tuttavia, in alcune parti del sistema è più conveniente grazie alla leggibilità di lavorare con i doppi. Quindi dobbiamo convertire tra il valore lungo che assume 9 cifre decimali in doppio.

Troviamo semplicemente il prendere il lungo e dividendo per 10 alla potenza di 9 o moltiplicando per 1 diviso per 10 alla potenza di 9 fornisce rappresentazioni imprecise in un doppio.

Per risolvere questo abbiamo usato il Math.Round(value,9) per dare i valori precisi.

Tuttavia, Math.Round() è orribilmente lento per le prestazioni.

Quindi la nostra idea al momento è quella di convertire direttamente la mantissa e l'esponente nel formato binario di un doppio poiché - in questo modo, non ci sarà bisogno di arrotondare.

Abbiamo imparato online come esaminare i bit di un doppio per ottenere la mantissa e l'esponente, ma è confuso capire come invertire ciò per prendere una mantissa ed esponente e fabbricare un doppio usando i bit.

Qualche suggerimento?

[Test] 
public unsafe void ChangeBitsInDouble() 
{ 
    var original = 1.0D; 
    long bits; 
    double* dptr = &original; 
    //bits = *(long*) dptr; 
    bits = BitConverter.DoubleToInt64Bits(original); 
    var negative = (bits < 0); 
    var exponent = (int) ((bits >> 52) & 0x7ffL); 
    var mantissa = bits & 0xfffffffffffffL; 
    if(exponent == 0) 
    { 
     exponent++; 
    } 
    else 
    { 
     mantissa = mantissa | (1L << 52); 
    } 
    exponent -= 1075; 

    if(mantissa == 0) 
    { 
     return; 
    } 

    while ((mantissa & 1) == 0) 
    { 
     mantissa >>= 1; 
     exponent++; 
    } 

    Console.WriteLine("Mantissa " + mantissa + ", exponent " + exponent); 

} 
+3

Sei sicuro che il valore che hai è esattamente rappresentabile in un 'doppio? – Justin

+0

forse questo sarà di aiuto, non voglio leggere tutto solo per aiutarti: P http://steve.hollasch.net/cgindex/coding/ieeefloat.html – MrFox

risposta

1

Non si deve usare un fattore di scala di 10^9, si consiglia di utilizzare 2^30, invece.

+0

Thansk ci stavamo solo rendendo conto che il codice sopra non è t completo. E imparare che i doppi esponenti come rappresentati nei bit usano un esponente binario piuttosto che un esponente decimale. Quindi la tua risposta rende un mondo di buon senso. – Wayne

+0

Questo renderà la rappresentazione a lungo illeggibile, ma migliorerà notevolmente le prestazioni e la conversione al doppio sarà alleggerita rapidamente. Quindi è un compromesso eccellente. Grazie! – Wayne

+0

Beh, sembra logico, ma in fase di test dopo aver diviso il fattore per riconvertirlo in doppio, risulta impreciso: var convert = 1 << 30; doppio prezzo = 45,454945768D; long result1 = (long) (price * convert); double result2 = ((double) result1)/convert; Assert.AreEqual (risultato2, prezzo); – Wayne

0

Come già avvenuto per l'altra risposta, il doppio funziona con il punto binario a virgola mobile anziché con virgola mobile e quindi l'approccio iniziale non funziona.

Non è inoltre chiaro se possa funzionare con una formula volutamente semplificata, perché non è chiaro quale sia l'intervallo massimo necessario, quindi l'arrotondamento diventa inevitabile.

Il problema di farlo in modo rapido ma preciso è ben studiato e spesso supportato dalle istruzioni della CPU. La tua unica possibilità di battere le conversioni built-in è:

  1. Hai raggiunto un traguardo matematico che è degno di alcuni seri articoli scritti a riguardo.
  2. Escludi casi sufficienti che non si verificheranno nei tuoi stessi esempi che mentre i built-in sono migliori generalmente il tuo è ottimizzato per il tuo uso personale.

A meno che il campo di valori che si utilizza sia molto limitato, il potenziale di cortocircuito sulla conversione tra IEEE 754 a precisione doppia e intero lungo diventa sempre più piccolo.

Se sei al punto in cui devi coprire la maggior parte dei casi di cover IEEE 754, o anche una proporzione considerevole, allora finirai per rendere le cose più lente.

Suggerirei di stare con ciò che avete, spostando le valigie dove double è più comodo da attaccare a lungo nonostante l'inconveniente, o se necessario usando decimal.È possibile creare un decimal da un long facilmente con:

private static decimal DivideByBillion (long l) 
{ 
    if(l >= 0) 
    return new decimal((int)(l & 0xFFFFFFFF), (int)(uint)(l >> 32), 0, false, 9); 
    l = -l; 
    return new decimal((int)(l & 0xFFFFFFFF), (int)(uint)(l >> 32), 0, true, 9); 
} 

Ora, decimal è grandezze più lenta da utilizzare in aritmetica di double (proprio perché implementa un approccio simile al tuo nella domanda iniziale, ma con un esponente che varia e mantissa più grande). Ma se hai bisogno solo di un modo conveniente per ottenere un valore per la visualizzazione o il rendering in stringa, allora l'hacking della conversione in decimal presenta vantaggi rispetto alla conversione a mano della conversione in double, quindi è possibile considerare .

+0

Le prestazioni sono ancora molto importanti nel codice in cui vengono utilizzati i doppi. È solo che sono plug-in utente e gli utenti si aspettano di utilizzare i numeri in virgola mobile. Usare decimal è certamente pratico per gli utenti ma deludente per le prestazioni di qualsiasi cosa facciano con loro. Dato che tutti i nostri input provengono dai doppi (dalla registrazione di stringhe o doppie come binari), allora sto pensando di estrarre la mantissa ... facendo matematica con quello ... quindi semplicemente sostituendo la mantissa per l'output. Si spera che eviterai arrotondamenti e mancanza di precisione. – Wayne

Problemi correlati