2012-12-24 15 views
5

Sto implementando un tipo numerico 31.32 con segno fisso a 64 bit in C#, basato su long. Fin qui tutto bene per l'aggiunta e la sottrazione. La moltiplicazione ha tuttavia un caso fastidioso che sto cercando di risolvere.Errore di moltiplicazione a virgola fissa a 64 bit

Il mio algoritmo attuale consiste nel suddividere ciascun operando nei suoi 32 bit più o meno significativi, eseguendo 4 moltiplicazioni in 4 long e aggiungendo i bit rilevanti di questi long. Qui è nel codice:

public static Fix64 operator *(Fix64 x, Fix64 y) { 

    var xl = x.m_rawValue; // underlying long of x 
    var yl = y.m_rawValue; // underlying long of y 

    var xlow = xl & 0x00000000FFFFFFFF; // take the 32 lowest bits of x 
    var xhigh = xl >> 32; // take the 32 highest bits of x 
    var ylow = yl & 0x00000000FFFFFFFF; // take the 32 lowest bits of y 
    var yhigh = yl >> 32; // take the 32 highest bits of y 

    // perform multiplications 
    var lowlow = xlow * ylow; 
    var lowhigh = xlow * yhigh; 
    var highlow = xhigh * ylow; 
    var highhigh = xhigh * yhigh; 

    // take the highest bits of lowlow and the lowest of highhigh 
    var loResult = lowlow >> 32; 
    var midResult1 = lowhigh; 
    var midResult2 = highlow; 
    var hiResult = highhigh << 32; 

    // add everything together and build result 
    var finalResult = loResult + midResult1 + midResult2 + hiResult; 
    return new Fix64(finalResult); // this constructor just copies the parameter into m_rawValue 
} 

Questo funziona nel caso generale, ma non riesce in un numero di scenari. Vale a dire, il risultato è disattivato di 1,0 (valore decimale), spesso per valori estremamente piccoli o grandi degli operandi. Qui ci sono alcuni risultati dal mio test di unità (FromRaw() è un metodo che costruisce una Fix64 direttamente da un lungo valore, senza spostare di esso):

Failed for FromRaw(-1) * FromRaw(-1): expected 0 but got -1 
Failed for FromRaw(-4) * FromRaw(6791302811978701836): expected -1.4726290525868535041809082031 but got -2,4726290525868535041809082031 
Failed for FromRaw(2265950765) * FromRaw(17179869183): expected 2.1103311001788824796676635742 but got 1,1103311001788824796676635742 

Sto cercando di capire la logica di questo su carta ma sono un po 'bloccato. Come posso risolvere questo?

+0

Cosa stai facendo con i bit di trasporto? Inoltre, non sto capendo la traduzione ... qual è il valore numerico equivalente di '2265950765', per esempio? – mellamokb

+0

Sì, per quanto riguarda i bit di trasporto? –

+0

Non ho familiarità con le regole di promozione dei numeri interi di C# - i valori 'lowlow' ecc. 32-bit, o la moltiplicazione 32x32 fornisce automaticamente un risultato a 64-bit? – hobbs

risposta

5

L'algoritmo sembra valido e ha funzionato "sulla carta" e sembra giusto. Qui ci sono i miei appunti elaborati per FromRaw(2265950765) * FromRaw(17179869183) (0,52758277510292828083038330078125 * 3,99999999976716935634613037109375 = 2,11033110017888247966766357421875)

x1 = 2265950765 
y1 = 17179869183 

xlow = 2265950765 
xhigh = 0 
ylow = 4294967295 
yhigh = 3 

lowlow = 9732184427755230675 
lowhigh = 6797852295 
highlow = 0 
highhigh = 0 

loResult = 2265950764 
midResult1 = 6797852295 
midResult2 = 0 
hiResult = 0 

finalResult = 9063803059 

Ora qui è quello che ho il sospetto che sta accadendo: lowlowesigenze essere un ulong per il risultato a venire fuori bene, ma penso che quello che ottieni è un valore firmato. Interpretato come firmato, lowlow termina con -8714559645954320941 (troppo basso di 2^64), loResult termina con -2029016532 (troppo basso di 2^32), finalResult termina con 4768835763 (anche troppo basso di 2^32) e il valore risultante è quindi 1.11033110017888247966766357421875 che è esattamente 1 in meno di quanto previsto.

In generale i tuoi valori devono essere considerati come aventi una "metà superiore" e una "metà inferiore" senza segno. highhigh è firmato * firmato = firmato; lowhigh e highlow sono firmati * non firmati = firmati; ma lowlow non è firmato * unsigned = unsigned.

+0

Esattamente a destra; i termini bassi devono essere non firmati. –

+0

@StephenCanon ha! Fantastico vederti qui. Non so se ricordi, ma mi hai aiutato con un problema molto simile qualche anno fa, motivo per cui ora lo capisco :) – hobbs

+0

Se xlow e ylow non sono firmati, devo inserire un cast per fare xlow * yhigh e xhigh * ylow, perché C# non mi permette di moltiplicare i longheroni firmati e non firmati insieme. Devo trasmettere i valori alti a unsigned e quindi il risultato torna a firmato? Questo mi confonde. – Asik

0

Non capisco, perché FromRaw(-1) * FromRaw(-1) deve restituire 0? Dovrebbe restituire +1

Generalmente su algoritmo: non dividere, solo moltiplicare long s.

Supponiamo di moltiplicare 2.3*4.5. Otterrete 10.35. Ma se moltiplichi 23*45, otterrai 1035.

Le cifre sono le stesse!

Quindi, per moltiplicare i numeri, è necessario moltiplicare m_rawValue s e quindi spostare i bit a destra.

+2

no, l'algoritmo cross-multiply è corretto. Se "si moltiplica e poi si sposta" si trabocca prima di spostarsi e i risultati non sono corretti. Per ottenere un multiplo 64x64 = 64, devi dividerlo in quattro multipli 32x32 = 64. – hobbs

+0

Il valore grezzo -1 rappresenta -0,00 ... 0024 qualcosa, il più piccolo valore negativo che il mio tipo può rappresentare. Quindi se lo si moltiplica da solo, dà un risultato troppo piccolo per essere rappresentato e quindi dovrebbe essere arrotondato a 0. – Asik

Problemi correlati