2009-04-20 18 views
10

J'ai un projet dans lequel une fonction reçoit quatre caractères de 8 bits et doit convertir le flottant IEEE-754 résultant de 32 bits en un nombre Perl normal. Il semble qu'il devrait y avoir un moyen plus rapide que le code de travail ci-dessous, mais je n'ai pas été en mesure de trouver une fonction de pack plus simple qui fonctionne.Comment puis-je convertir quatre caractères en un flottant IEEE-754 32 bits en Perl?

Il ne fonctionne pas, mais il semble que il est proche:

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

Travaux:

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

Quelqu'un at-il une meilleure façon?

+1

Je suis intriguée par le fait que votre extrait de code "ne fonctionne pas mais est proche" - pouvez-vous identifier les différences? Par exemple. en prenant le résultat de unpack() et en le convertissant aux 4 octets, puis en recherchant des bits qui sont différents entre l'entrée et la sortie finale? –

Répondre

13

Je prendrais l'approche inverse: oubliez le déballage, tenez-vous à un peu de twittling.

D'abord, assemblez votre mot de 32 bits. Selon boutisme, il faudra peut-être l'inverse:

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

Maintenant extrait les parties du mot: le bit de signe, exposant et mantisse:

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

Assemblez votre flotteur:

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

Il existe quelques exemples sur Wikipedia.

  • Testé sur 0xC2ED4000 => -118.625 et cela fonctionne.
  • Testé sur 0x3E200000 => 0.15625 et trouvé un bug! (Fixe)
  • Ne pas oublier de traiter et infinités NaN quand expo $ de == 255
+0

Très bien. Cela fonctionne et est deux fois plus rapide pour mon cas de test et les réponses sont correctes (testé environ 100k numéros uniques). À l'avenir, je vais devoir essayer de me salir les mains et essayer de faire quelques opérations. Merci! –

+0

Pas de soucis, profitez-en. Devrait être beaucoup rapide. PS: "quand $ expo == 255" devrait lire "quand expo == 128" ... j'ai oublié le décalage. – NickZoic

+0

my $ word = décompresser ("N", $ octets); devrait être beaucoup plus rapide –

5

La meilleure façon de le faire est d'utiliser pack().

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

Ou si la source et la destination ont différentes boutisme:

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

Vous dites que cette méthode « ne fonctionne pas - il semble que c'est proche » et « échoue pour un petit nombre », mais vous ne donnez pas d'exemple. J'imagine que ce que vous voyez est arrondi où, par exemple, un nombre est emballé comme 1.234, mais il est déballé comme 1.23399996757507. Ce n'est pas une fonction de pack(), mais de la précision d'un flottant de 4 octets.

+1

Vous pourriez bien avoir raison. D'un autre côté, mon code produirait exactement les mêmes erreurs, je pense. Pack/unpack 'f' effectue des conversions vers/à partir de "Un flottant à simple précision dans le format natif". ... peut-être que tout ce qui marche sur Dan n'est pas * tout à fait * IEEE-754? – NickZoic

+1

"Mon code produirait exactement les mêmes erreurs". Votre code * produit * les mêmes résultats que la méthode pack() mais ce n'est pas une erreur, mais simplement un arrondi. Par exemple, essayez de décompresser 0x3F9DF3B6 qui est 1.234 emballé comme un flottant en utilisant votre méthode et la méthode pack. En outre, le format flottant "natif" de la grande majorité des systèmes que Perl exécute ces jours-ci est IEEE 754. Si l'OP était sur un système avec un format flottant différent, il serait assez inhabituel qu'il le sache. – jmcnamara

+0

L'arrondi est un problème pour quelque chose comme e-39 –