2009-04-20 7 views
10

Mam projekt, w którym funkcja otrzymuje cztery znaki 8-bitowe i musi przekonwertować wynikowy 32-bitowy zmiennoprzecinkowy IEEE-754 na zwykły numer Perla. Wygląda na to, że powinien istnieć szybszy sposób niż działający kod poniżej, ale nie byłem w stanie znaleźć prostszej funkcji, która działa.Jak mogę przekonwertować cztery znaki do 32-bitowego float IEEE-754 w Perlu?

To nie działa, ale wydaje się, że jest w pobliżu:

$float = unpack("f", pack("C4", @array[0..3]); # Fails for small numbers 

Works:

@bits0 = split('', unpack("B8", pack("C", shift))); 
@bits1 = split('', unpack("B8", pack("C", shift))); 
@bits2 = split('', unpack("B8", pack("C", shift))); 
@bits3 = split('', unpack("B8", pack("C", shift))); 
push @bits, @bits3, @bits2, @bits1, @bits0; 

$mantbit = shift(@bits); 
$mantsign = $mantbit ? -1 : 1; 
$exp = ord(pack("B8", join("",@bits[0..7]))); 
splice(@bits, 0, 8); 

# Convert fractional float to decimal 
for (my $i = 0; $i < 23; $i++) { 
    $f = $bits[$i] * 2 ** (-1 * ($i + 1)); 
    $mant += $f; 
} 
$float = $mantsign * (1 + $mant) * (2 ** ($exp - 127)); 

Ktoś ma lepszy sposób?

+1

Jestem zaintrygowany tym, że Twój urywek "nie działa, ale jest blisko" - czy potrafisz wskazać różnice? Na przykład. biorąc wynik rozpakowania() i przekształcenia go z powrotem na 4 bajty, a następnie szukając bitów, które różnią się między wejściowym a końcowym wyjściem? –

Odpowiedz

13

Podejdę w odwrotnym kierunku: zapomnij rozpakować, przykleić do twidrowania.

Najpierw połącz 32-bitowe słowo. W zależności od endianness, może to być na odwrót:

my $word = ($byte0 << 24) + ($byte1 << 16) + ($byte2 << 8) + $byte3; 

Teraz wyodrębnić części słowem: bit znaku, wykładnik i mantysy:

my $sign = ($word & 0x80000000) ? -1 : 1; 
my $expo = (($word & 0x7F800000) >> 23) - 127; 
my $mant = ($word & 0x007FFFFF | 0x00800000); 

zainstalować float:

my $num = $sign * (2 ** $expo) * ($mant/(1 << 23)); 

Istnieje kilka przykładów na temat Wikipedia.

  • Testowano to na 0xC2ED4000 => -118,625 i to działa.
  • Testowano to na 0x3E200000 => 0.15625 i znalazłem błąd! (Stałe)
  • Nie zapomnij obsłużyć nieskończoności i Nans gdy $ == 255
+0

Bardzo ładne. To działa i jest dwa razy szybsze dla mojego przypadku testowego, a odpowiedzi są poprawne (testowane około 100 tys. Unikalnych numerów). W przyszłości będę musiał spróbować zabrudzić sobie ręce i spróbować wykonać trochę operacji. Dzięki! –

+0

Bez obaw, ciesz się. Powinno być dużo szybkie. PS: "kiedy $ expo == 255" powinno brzmieć "kiedy $ expo == 128" ... Zapomniałem przesunięcia. – NickZoic

+0

my $ word = unpack ("N", $ bajtów); powinien być znacznie szybszy –

5

Najlepszym sposobem, aby to zrobić jest użycie expo pack().

my @bytes = (0xC2, 0xED, 0x40, 0x00); 
my $float = unpack 'f', pack 'C4', @bytes; 

Albo jeśli źródłowy i docelowy mają różne endianness:

my $float = unpack 'f', pack 'C4', reverse @bytes; 

Można powiedzieć, że ta metoda „nie działa - wydaje się, że jest blisko” i „nie dla małych liczb”, ale nie dajesz przykładu. Domyślam się, że to, co właśnie widzisz, jest zaokrąglane, gdy na przykład liczba jest spakowana jako 1.234, ale jest rozpakowywana jako 1.23399996757507. Nie jest to funkcja pack(), ale precyzja 4-bajtowego float.

+1

Możesz mieć rację. Z drugiej strony, mój kod generowałby dokładnie te same błędy, jak sądzę. spakuj/rozpakuj "f" powoduje konwersje do/z "Pętla pojedynczej precyzji w natywnym formacie." ... może to, na co Dan biegnie, nie jest * całkiem * IEEE-754? – NickZoic

+1

"Mój kod wygenerowałby dokładnie te same błędy". Twój kod * nie * daje takie same wyniki, jak metoda pack(), ale nie jest błędem, tylko zaokrągleniem. Na przykład spróbuj rozpakować plik 0x3F9DF3B6, który jest 1.234 spakowany jako float, przy użyciu metody i metody pack. Ponadto, natywny format zmiennoprzecinkowy ogromnej większości systemów, które perl działa w tych dniach, to IEEE 754. Jeśli OP byłby w systemie z innym formatem float, byłoby dość niezwykłe, by wiedział o tym. – jmcnamara

+0

Zaokrąglanie jest problemem dla czegoś podobnego do e-39 –

Powiązane problemy