2014-09-10 3 views
5

Dato un valore rubino Float, ad esempio,Conversione di un virgola mobile al relativo bit-segmenti

f = 12.125 

Vorrei concludere una matrice 3-elemento contenente il segno del numero in virgola mobile (1 bit), esponente (11 bit) e frazione (52 bit). (I float di Ruby sono la rappresentazione a 64 bit a doppia precisione IEEE 754)

Qual è il modo migliore per farlo? La manipolazione a livello di bit non sembra essere il punto di forza di Ruby.

Nota che voglio i bit, non i valori numerici a cui corrispondono. Ad esempio, ottenere [0, -127, 1] per il valore a virgola mobile di 1.0 non è quello che sto cercando: voglio i bit effettivi in ​​forma di stringa o una rappresentazione equivalente, come ["0", "0ff", "000 0000 0000"].

+0

John, grazie per la menzione della rappresentazione [IEEE 754] (http://en.wikipedia.org/wiki/IEEE_floating_point) di float. Non ero conscio di ciò.Se qualche lettore ha bisogno di un promemoria, viene fornito un esempio di calcolo [qui] (http://class.ece.iastate.edu/arun/cpre381/ieee754/ie4.html). –

risposta

4

I dati di bit possono essere esposti tramite le matrici pack poiché Float non fornisce funzioni internamente.

str = [12.125].pack('D').bytes.reverse.map{|n| "%08b" %n }.join 
=> "0100000000101000010000000000000000000000000000000000000000000000" 

[ str[0], str[1..11], str[12..63] ] 
=> ["0", "10000000010", "1000010000000000000000000000000000000000000000000000"] 

Questo è un po '"intorno alle case" per estrarlo da una rappresentazione di stringa. Sono sicuro che ci sia un modo più efficiente per estrarre i dati dal originale bytes ...


Modifica La manipolazione livello di bit ottimizzato il mio interesse così ho avuto un poke intorno. Per utilizzare le operazioni in Ruby è necessario avere un numero intero in modo che il float richieda ancora un po 'di unpack per convertire in un int di 64 bit. La rappresentazione documentata di big endian/ieee754 è abbastanza banale. La rappresentazione little endian di cui non sono così sicuro. È un po 'strano, dato che non ci sono limiti di byte completi con un esponente a 11 bit e mantissa a 52 bit. È troppo difficile estrarre i bit e scambiarli per ottenere ciò che somiglia a little endian, e non è sicuro se sia giusto perché non ho visto alcun riferimento al layout. Quindi il valore a 64 bit è little endian, non sono troppo sicuro di come ciò si applichi ai componenti del valore a 64 bit finché non li si memorizza da qualche altra parte, come un int a 16 bit per la mantissa.

Come esempio per un valore a 11 bit da piccolo> grande, il tipo di cosa che stavo facendo era spostare il byte più significativo rimasto 3 in primo piano, quindi OR con i 3 bit meno significativi.

v = 0x4F2 
((v & 0xFF) << 3) | (v >> 8)) 

Qui è comunque, si spera sia di qualche utilità.

class Float 
    Float::LITTLE_ENDIAN = [1.0].pack("E") == [1.0].pack("D") 

    # Returns a sign, exponent and mantissa as integers 
    def ieee745_binary64 
    # Build a big end int representation so we can use bit operations 
    tb = [self].pack('D').unpack('Q>').first 

    # Check what we are 
    if Float::LITTLE_ENDIAN 
     ieee745_binary64_little_endian tb 
    else 
     ieee745_binary64_big_endian tb 
    end 
    end 

    # Force a little end calc 
    def ieee745_binary64_little 
    ieee745_binary64_little_endian [self].pack('E').unpack('Q>').first 
    end 

    # Force a big end calc 
    def ieee745_binary64_big 
    ieee745_binary64_big_endian [self].pack('G').unpack('Q>').first 
    end 

    # Little 
    def ieee745_binary64_little_endian big_end_int 
    #puts "big #{big_end_int.to_s(2)}" 
    sign  = (big_end_int & 0x80 ) >> 7 

    exp_a = (big_end_int & 0x7F ) << 1 # get the last 7 bits, make it more significant 
    exp_b = (big_end_int & 0x8000) >> 15 # get the 9th bit, to fill the sign gap 
    exp_c = (big_end_int & 0x7000) >> 4 # get the 10-12th bit to stick on the front 
    exponent = exp_a | exp_b | exp_c 

    mant_a = (big_end_int & 0xFFFFFFFFFFFF0000) >> 12 # F000 was taken above 
    mant_b = (big_end_int & 0x0000000000000F00) >> 8 # F00 was left over 
    mantissa = mant_a | mant_b 

    [ sign, exponent, mantissa ] 
    end 

    # Big 
    def ieee745_binary64_big_endian big_end_int 
    sign  = (big_end_int & 0x8000000000000000) >> 63 
    exponent = (big_end_int & 0x7FF0000000000000) >> 52 
    mantissa = (big_end_int & 0x000FFFFFFFFFFFFF) >> 0 

    [ sign, exponent, mantissa ] 
    end 
end 

e test ...

def printer val, vals 
    printf "%-15s sign|%01b|\n",   val,  vals[0] 
    printf " hex e|%3x|   m|%013x|\n", vals[1], vals[2] 
    printf " bin e|%011b| m|%052b|\n\n",  vals[1], vals[2] 
end 

floats = [ 12.125, -12.125, 1.0/3, -1.0/3, 1.0, -1.0, 1.131313131313, -1.131313131313 ] 

floats.each do |v| 
    printer v, v.ieee745_binary64 
    printer v, v.ieee745_binary64_big 
end 

TIL mio cervello è big endian! Noterai che gli int che stanno lavorando sono entrambi big endian. Ho fallito un po 'spostando l'altro modo.

+0

Questo è sicuramente il più vicino delle risposte finora. +1! –

+0

(Si noti che 'G' sceglie sempre big-endian, quindi non è necessariamente il formato nativo come per la mia domanda.x86 e x86_64 sono little-endian, per esempio, e avrebbe bisogno di essere invertito.) –

+0

' D' e retromarcia poi? o semplice 'D'? – Matt

3

Utilizzare frexp dal modulo Math. Dal doc:

fraction, exponent = Math.frexp(1234) #=> [0.6025390625, 11] 
fraction * 2**exponent     #=> 1234.0 

Il bit di segno è facile da trovare da solo.

+0

I valori restituiti da 'frexp' non corrispondono esattamente alla convenzione IEEE 754, sebbene siano equivalenti. Se sei veramente interessato ai bit, devi tenerne conto. I motivi (probabilmente storici) sono indovinati in http://stackoverflow.com/questions/24928833/why-does-frexp-not-yield-scientific-notation –

+0

Sono a conoscenza di 'frexp', ma voglio l'effettivo bit, non i valori numerici a cui corrispondono - ad esempio, per l'esponente mi aspetterei di vedere una stringa di 11 caratteri di 0 e 1, o una stringa di 3 caratteri di cifre esadecimali, ecc. –