首页 > 解决方案 > perl - 解析字节长度为 3,5,6,7 的有符号整数

问题描述

我有一个业务需求来读取字节长度从 1 到 8 不等的带符号整数值。

int 值的标准实现占用 2**n 数量级的字节

> perl -e ' $x=chr(253); $y=unpack "c",$x; printf("%d\n",$y) ' # 1 byte
-3
> perl -e ' $x=chr(255).chr(253); $y=unpack "s>",$x; printf("%d\n",$y)' # 2 byte
-3
> perl -e ' $x=chr(255) x 3;$x.=chr(253); $y=unpack "i>",$x; printf("%d\n",$y) ' # 4 byte
-3
> perl -e ' $x=chr(255) x 7;$x.=chr(253); $y=unpack "q>",$x; printf("%d\n",$y) ' # 8 byte
-3
>

对于 3、5、6、7 字节,我正在尝试如下

> perl -e ' $x=chr(255) x 2;$x.=chr(253); $y=unpack "i>",$x; printf("%d\n",$y) '
0
>

但这是错误的,我需要-3。

这个链接Decoding 3-byte integer in Perl回答了无符号数,但没有解决我的问题。

有人可以帮助获得 3、5、6、7 字节的有符号值吗?

标签: perl

解决方案


要扩展 2 的补码整数的大小,必须使用符号扩展。这意味着您需要将数字的符号位复制到您添加的每个位中。

  +---+---+---+---+---+---+---+------+
  |   |   |   |   |   |   |   |      |
  v   v   v   v   v   v   v   v      |
+---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+--
|   |   |   |   |   |   |   |   |  |   |   |   |   |   |   |   |   | ...
+---+---+---+---+---+---+---+---+  +---+---+---+---+---+---+---+---+--
MSB         new byte          LSB  MSB        old byte(s)        LSB

所以0000 0011(3) 变成0000 0000 0000 0011(3)
并且1111 1101(-3) 变成1111 1111 1111 1101(-3)。

一般解决方案:

  • 解压一个 1 到 8 字节的大端数字:

    unpack( "q>", substr( ( ord($_) & 0x80 ? "\xFF"x7 : "\x00"x7 ) . $_, -8 ) )
    

    测试:

    $ perl -e'
       for ( map { ( "\x00" x $_ ) . "\x03", ( "\xFF" x $_ ) . "\xFD" } 0..7 ) {
          printf "%v02X => %d\n",
             $_, unpack( "q>", substr( ( ord($_) & 0x80 ? "\xFF"x7 : "\x00"x7 ) . $_, -8 ) );
       }
    '
    03 => 3
    FD => -3
    00.03 => 3
    FF.FD => -3
    00.00.03 => 3
    FF.FF.FD => -3
    00.00.00.03 => 3
    FF.FF.FF.FD => -3
    00.00.00.00.03 => 3
    FF.FF.FF.FF.FD => -3
    00.00.00.00.00.03 => 3
    FF.FF.FF.FF.FF.FD => -3
    00.00.00.00.00.00.03 => 3
    FF.FF.FF.FF.FF.FF.FD => -3
    00.00.00.00.00.00.00.03 => 3
    FF.FF.FF.FF.FF.FF.FF.FD => -3
    
  • 解压一个 1 到 8 字节的小端数字:

    unpack( "q<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF"x7 : "\x00"x7 ) )
    

    测试:

    $ perl -e'
       for ( map { "\x03" . ( "\x00" x $_ ), "\xFD" . ( "\xFF" x $_ ) } 0..7 ) {
          printf "%v02X => %d\n",
             $_, unpack( "q<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF"x7 : "\x00"x7 ) );
       }
    '
    03 => 3
    FD => -3
    03.00 => 3
    FD.FF => -3
    03.00.00 => 3
    FD.FF.FF => -3
    03.00.00.00 => 3
    FD.FF.FF.FF => -3
    03.00.00.00.00 => 3
    FD.FF.FF.FF.FF => -3
    03.00.00.00.00.00 => 3
    FD.FF.FF.FF.FF.FF => -3
    03.00.00.00.00.00.00 => 3
    FD.FF.FF.FF.FF.FF.FF => -3
    03.00.00.00.00.00.00.00 => 3
    FD.FF.FF.FF.FF.FF.FF.FF => -3
    

具体解决方案:

  • 解压一个 3 字节的大端数字:

    unpack( "l>", ( ord($_) & 0x80 ? "\xFF" : "\x00" ) . $_ )
    
  • 解压一个 3 字节的 little-endian 数字:

    unpack( "l<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF" : "\x00" ) )
    
  • 解压一个 5 字节的 big-endian 数字:

    unpack( "q>", ( ord($_) & 0x80 ? "\xFF"x3 : "\x00"x3 ) . $_ )
    
  • 解压一个 5 字节的 little-endian 数字:

    unpack( "q<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF"x3 : "\x00"x3 ) )
    
  • 解压一个 6 字节的 big-endian 数字:

    unpack( "q>", ( ord($_) & 0x80 ? "\xFF"x2 : "\x00"x2 ) . $_ )
    
  • 解压一个 6 字节的 little-endian 数字:

    unpack( "q<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF"x2 : "\x00"x2 ) )
    
  • 解压一个 7 字节的大端数字:

    unpack( "q>", ( ord($_) & 0x80 ? "\xFF" : "\x00" ) . $_ )
    
  • 解压一个 7 字节的 little-endian 数字:

    unpack( "q<", $_ . ( ord(substr($_, -1)) & 0x80 ? "\xFF" : "\x00" ) )
    

推荐阅读