11

我有一个项目,其中一个函数接收四个 8 位字符,需要将生成的 32 位 IEEE-754 浮点数转换为常规 Perl 数字。似乎应该有比下面的工作代码更快的方法,但我无法找出一个更简单的 pack 函数。

它不起作用,但似乎很接近:

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

作品:

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

有人有更好的方法吗?

4

2 回答 2

14

我会采取相反的方法:忘记拆包,坚持玩弄。

首先,组装你的 32 位字。根据字节顺序,这可能必须是相反的:

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

现在提取单词的部分:符号位、指数和尾数:

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

组装你的花车:

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

维基百科上有一些例子。

  • 在 0xC2ED4000 => -118.625 上对此进行了测试,并且可以正常工作。
  • 在 0x3E200000 => 0.15625 上对此进行了测试,发现了一个错误!(固定的)
  • 不要忘记在 $expo == 255 时处理无穷大和 NaN
于 2009-04-20T22:54:34.890 回答
5

最好的方法是使用pack()

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

或者,如果源和目标具有不同的字节序:

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

您说这种方法“不起作用-似乎很接近”和“对小数字失败”,但您没有举例。我猜你实际上看到的是四舍五入,例如,一个数字被打包为 1.234,但它被解包为 1.23399996757507。这不是 pack() 的函数,而是 4 字节浮点数的精度。

于 2009-04-21T08:09:20.797 回答