Как я могу преобразовать четыре символа в 32-разрядный код IEEE-754 в Perl? - PullRequest
10 голосов
/ 21 апреля 2009

У меня есть проект, в котором функция получает четыре 8-битных символа и должна преобразовать результирующий 32-битный IEEE-754 с плавающей точкой в ​​обычный номер Perl. Кажется, что должен быть более быстрый способ, чем рабочий код, приведенный ниже, но я не смог выяснить более простую упаковочную функцию, которая работает.

Не работает, но кажется, что близко:

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

У кого-нибудь есть способ получше?

Ответы [ 2 ]

13 голосов
/ 21 апреля 2009

Я бы выбрал противоположный подход: забудь распаковать, придерживайся немного.

Сначала соберите 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));

В Wikipedia .

есть несколько примеров.
  • Протестировано на 0xC2ED4000 => -118.625 и все работает.
  • Протестировал это на 0x3E200000 => 0.15625 и обнаружил ошибку! (Фиксированный)
  • Не забывайте обрабатывать бесконечности и NaN, когда $ expo == 255
5 голосов
/ 21 апреля 2009

Лучший способ сделать это - использовать 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-байтового числа с плавающей запятой.

...