11

関数が 4 つの 8 ビット文字を受け取り、結果の 32 ビット IEEE-754 float を通常の 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 バイトの float の精度の関数です。

于 2009-04-21T08:09:20.797 に答える