如何在 Perl 中将十六进制解码为负整数值(第 2 部分)

How to decode hex to negative integer value in Perl (Part 2)

我对十六进制格式的负整数值有疑问。我的工作有 2 种格式,如下例所示:

###########################################################################

=for comment

B4 80 B5 FF 07 =  2146254900
B5 80 B5 FF 07 =  2146254901

A9 B4 E8 8C 08 = -2120607191
AA B4 E8 8C 08 = -2120607190

A3 86 8D 8E FC FF FF FF FF 01 = -1044167901
A4 86 8D 8E FC FF FF FF FF 01 = -1044167900

=cut

###########################################################################

$vars[0] = "\xB4\x80\xB5\xFF\x07";
$vars[1] = "\xB5\x80\xB5\xFF\x07";
$vars[2] = "\xA9\xB4\xE8\x8C\x08";
$vars[3] = "\xAA\xB4\xE8\x8C\x08";
$vars[4] = "\xA3\x86\x8D\x8E\xFC\xFF\xFF\xFF\xFF\x01";
$vars[5] = "\xA4\x86\x8D\x8E\xFC\xFF\xFF\xFF\xFF\x01";

###########################################################################

for my $var (@vars)
{
    print decodeInt($var) . "\n";
}

###########################################################################

sub decodeInt
{
    $_[0] =~ s/^([\x80-\xFF]*[\x00-\x7F])// or return;

    my $encoded_num = ;
    my $num = 0;

    for ( reverse unpack 'C*', $encoded_num )
    {
        $num = ( $num << 7 ) | ( $_ & 0x7F );
    }

    $num = unpack 'q', pack 'Q', $num;  #Cast two unsigned complement Ints back to 64-bit signed

    return $num;
}

##########################################################################

以前,上面的代码工作得很好。但现在我有了新的负整数值格式,但它不起作用。

A9 B4 E8 8C 08 = -2120607191
AA B4 E8 8C 08 = -2120607190

我的代码解码为 2174360105 和 2174360106。

$num = unpack 'q', pack 'Q', $num;

用于 64 位有符号整数。对于 32 位值,您需要使用

$num = unpack 'l', pack 'L', $num;

$num -= 2**32;

也许下面的代码会转换为所需的结果

use strict;
use warnings;

use feature 'say';

use Data::Dumper;

my $debug = 0;

while( my $num = <DATA> ) {
    chomp $num;
    say hex2dec($num);
}

sub hex2dec {
    my $data = shift;

    my $sign    = 0;
    my $num     = 0;
    my $bits;

    my @data = reverse split ' ', $data;

    $bits = 32 if scalar @data == 5;
    $bits = 64 if scalar @data == 10;

    map { $num = ($num << 7)|h2i($_) } @data;

    $sign = ($num & (1<<($bits-1)) ) ? 1 : 0;

    if( $debug ) {
        say Dumper(\@data);
        say "bits : $bits";
        say "sign : $sign";
        say "num  : $num";
        num2bin( $num,$bits);
        num2bin(~$num,$bits);
    }

    if( $sign ) {
        $num = -1 * ((1<<$bits) - $num) if $bits == 32;
        $num = -1 * (~$num + 1)         if $bits == 64;
    }

    return $num;
}

# Hex byte to integer number
sub h2i {
    my $data = shift;

    my $r = 0;
    my %hex2dec;

    @hex2dec{0..9,'A'..'F'} = 0..15; 

    map { $r <<= 4; $r += $hex2dec{$_} } split '', $data;

    return $r&0x7F;
}

# Print integer number as bin 
sub num2bin {
    my $num = shift;
    my $bit = shift;

    my $bin;

    while( $bit-- ) {
        $bin .= $num & (1<<$bit) ? '1' : '0';
    }

    say $bin;
}

=for comment

B4 80 B5 FF 07 =  2146254900
B5 80 B5 FF 07 =  2146254901

A9 B4 E8 8C 08 = -2120607191
AA B4 E8 8C 08 = -2120607190

A1 E1 CE FD 0F = -5001055

A3 86 8D 8E FC FF FF FF FF 01 = -1044167901
A4 86 8D 8E FC FF FF FF FF 01 = -1044167900

=cut

__DATA__
B4 80 B5 FF 07
B5 80 B5 FF 07
A9 B4 E8 8C 08
AA B4 E8 8C 08
A1 E1 CE FD 0F
A3 86 8D 8E FC FF FF FF FF 01
A4 86 8D 8E FC FF FF FF FF 01

输出

2146254900
2146254901
-2120607191
-2120607190
-5001055
-1044167901
-1044167900

代码的其他变体

use strict;
use warnings;

use feature 'say';

use bytes;

my $debug = 1;

my @vars = ( "\xB4\x80\xB5\xFF\x07",
             "\xB5\x80\xB5\xFF\x07",
             "\xA9\xB4\xE8\x8C\x08",
             "\xAA\xB4\xE8\x8C\x08",
             "\xA1\xE1\xCE\xFD\x0F",
             "\xA3\x86\x8D\x8E\xFC\xFF\xFF\xFF\xFF\x01",
             "\xA4\x86\x8D\x8E\xFC\xFF\xFF\xFF\xFF\x01"
            );

foreach my $v (@vars) {
    say 'DATA   : ' 
        . decode($v)
        . '  :: '
        . unpack 'H'. bytes::length($v)*2, $v;
}

sub decode {
    my $data = shift;

    my $num = 0;
    my $bits;
    my $ret;

    map { $num = ($num << 7) | ($_ & 0x7F) } reverse unpack 'C*', $data;

    my $bytes = bytes::length($num);

    $bits = 32 if $bytes == 10;
    $bits = 64 if $bytes == 20;

    my $sign = $num & (1<<($bits-1)) ? 1 : 0;

    if( $sign ) {
        $ret = -1 * ((1<<$bits) - $num) if $bits == 32;
        $ret = -1 * (~$num + 1)         if $bits == 64;
    } else {
        $ret = $num;
    }

    if( $debug ) {
        say "\nDebug\n" . '-' x 20;
        say "data   : " . unpack 'H'. $bytes, $data;
        say "bytes  : $bytes";
        say "num    : $num (dec)";
        say "bits   : $bits";
        say "sign   : $sign";
        say "hex    : " . itoh($num);
        say "bin    : " . num2bin( $num, $bits);
        say "~bin   : " . num2bin(~$num, $bits);
        say "decode : $ret";
    }

    return $ret;
}

# Print integer number as bin 
sub num2bin {
    my $num = shift;
    my $bit = shift;

    my $bin;

    while( $bit-- ) {
        $bin .=  $num & (1<<$bit) ? '1' : '0';
    }

    return $bin;
}

# Convert integer to ascii hex
sub itoh {
    my $data = shift;

    my $hex;
    my %hex;

    @hex{0..15} = (0..9,'A'..'F');

    while( $data ) {
        my $n = $data & 0xF;
        $data >>= 4;
        $hex .= $hex{$n};
    }

    return reverse $hex;
}

输出

Debug
--------------------
data   : b480b5ff07
bytes  : 10
num    : 2146254900 (dec)
bits   : 32
sign   : 0
hex    : 7FED4034
bin    : 01111111111011010100000000110100
~bin   : 10000000000100101011111111001011
decode : 2146254900
DATA   : 2146254900  :: b480b5ff07

Debug
--------------------
data   : b580b5ff07
bytes  : 10
num    : 2146254901 (dec)
bits   : 32
sign   : 0
hex    : 7FED4035
bin    : 01111111111011010100000000110101
~bin   : 10000000000100101011111111001010
decode : 2146254901
DATA   : 2146254901  :: b580b5ff07

Debug
--------------------
data   : a9b4e88c08
bytes  : 10
num    : 2174360105 (dec)
bits   : 32
sign   : 1
hex    : 819A1A29
bin    : 10000001100110100001101000101001
~bin   : 01111110011001011110010111010110
decode : -2120607191
DATA   : -2120607191  :: a9b4e88c08

Debug
--------------------
data   : aab4e88c08
bytes  : 10
num    : 2174360106 (dec)
bits   : 32
sign   : 1
hex    : 819A1A2A
bin    : 10000001100110100001101000101010
~bin   : 01111110011001011110010111010101
decode : -2120607190
DATA   : -2120607190  :: aab4e88c08

Debug
--------------------
data   : a1e1cefd0f
bytes  : 10
num    : 4289966241 (dec)
bits   : 32
sign   : 1
hex    : FFB3B0A1
bin    : 11111111101100111011000010100001
~bin   : 00000000010011000100111101011110
decode : -5001055
DATA   : -5001055  :: a1e1cefd0f

Debug
--------------------
data   : a3868d8efcffffffff01
bytes  : 20
num    : 18446744072665383715 (dec)
bits   : 64
sign   : 1
hex    : FFFFFFFFC1C34323
bin    : 1111111111111111111111111111111111000001110000110100001100100011
~bin   : 0000000000000000000000000000000000111110001111001011110011011100
decode : -1044167901
DATA   : -1044167901  :: a3868d8efcffffffff01

Debug
--------------------
data   : a4868d8efcffffffff01
bytes  : 20
num    : 18446744072665383716 (dec)
bits   : 64
sign   : 1
hex    : FFFFFFFFC1C34324
bin    : 1111111111111111111111111111111111000001110000110100001100100100
~bin   : 0000000000000000000000000000000000111110001111001011110011011011
decode : -1044167900
DATA   : -1044167900  :: a4868d8efcffffffff01