2009-04-20 8 views
10

Ich habe ein Projekt, bei dem eine Funktion vier 8-Bit-Zeichen empfängt und die resultierende 32-Bit-IEEE-754-Gleitkommazahl in eine normale Perl-Zahl konvertieren muss. Es scheint, dass es einen schnelleren Weg geben sollte als der unten stehende Arbeitscode, aber ich konnte keine einfachere Pack-Funktion finden, die funktioniert.Wie kann ich in Perl vier Zeichen in einen 32-Bit IEEE-754 Float konvertieren?

Es funktioniert nicht, aber es scheint, wie es in der Nähe ist:

$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)); 

Wer einen besseren Weg?

+1

Ich bin fasziniert, dass Ihr Top-Snippet "nicht funktioniert, aber in der Nähe ist" - können Sie die Unterschiede feststellen? Z.B. indem man das Ergebnis von unpack() nimmt und es zurück in die 4 Bytes umwandelt, und dann nach Bits sucht, die zwischen Eingabe und endgültiger Ausgabe verschieden sind? –

Antwort

13

Ich würde den umgekehrten Weg gehen: vergessen Sie das Auspacken, bleiben Sie bei der Arbeit.

Zuerst montieren Sie Ihr 32-Bit-Wort. Je nach endianness, könnte dies umgekehrt sein müssen:

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

nun die Teile des Wortes extrahieren: das Vorzeichenbit, Exponent und Mantisse:

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

Stellen Sie sich Ihr float:

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

Es gibt einige Beispiele auf Wikipedia.

  • Getestet dies auf 0xC2ED4000 => -118.625 und es funktioniert.
  • Getestet auf 0x3E200000 => 0.15625 und einen Fehler gefunden! (Fest)
  • Vergessen Sie nicht Unendlichkeiten und NaNs zu behandeln, wenn $ expo == 255
+0

Sehr schön. Das funktioniert und ist für meinen Testfall doppelt so schnell und die Antworten sind korrekt (getestet über 100k eindeutige Zahlen). In der Zukunft muss ich versuchen, meine Hände schmutzig zu machen und einige Bitoperationen zu versuchen. Vielen Dank! –

+0

Keine Sorge, genießen. Sollte viel schnell sein. PS: "wenn $ expo == 255" sollte lesen "wenn $ expo == 128" ... Ich habe den Offset vergessen. – NickZoic

+0

my $ word = unpack ("N", $ bytes); sollte viel schneller sein –

5

Der beste Weg, dies zu tun ist pack() zu verwenden.

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

Oder wenn die Quelle und das Ziel haben unterschiedliche Endian:

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

Sie sagen, dass diese Methode „funktioniert nicht - es scheint, wie es in der Nähe ist“ und „nicht für kleine Zahlen“, aber Sie geben kein Beispiel. Ich vermute, dass das, was Sie tatsächlich sehen, das Runden ist, wo zum Beispiel eine Nummer als 1.234 gepackt wird, aber sie wird als 1.23399996757507 ausgepackt. Das ist keine Funktion von pack(), sondern von der Genauigkeit eines 4-Byte-Floats.

+1

Sie könnten gut Recht haben. Auf der anderen Seite würde mein Code genau die gleichen Fehler erzeugen, würde ich denken. packen/entpacken 'f' führt Konvertierungen zu/aus "Ein Gleitkomma mit einfacher Genauigkeit im nativen Format." ... vielleicht, was auch immer Dan läuft ist nicht ganz * IEEE-754? – NickZoic

+1

"Mein Code würde genau die gleichen Fehler erzeugen". Ihr Code * produziert * die gleichen Ergebnisse wie die Methode pack(), aber es handelt sich nicht um einen Fehler, sondern nur um eine Rundung. Versuchen Sie zum Beispiel, 0x3F9DF3B6 zu entpacken, das mit Ihrer Methode und der Packmethode als Float 1.234 gepackt ist. Außerdem ist das "native" Float-Format der meisten Systeme, die Perl an diesen Tagen ausführt, IEEE 754. Wenn das OP auf einem System mit einem anderen Float-Format lief, wäre es ungewöhnlich genug, dass er davon wusste. – jmcnamara

+0

Die Rundung ist ein Problem für so etwas wie e-39 –