表示有符号十六进制值所需的最小字节数

时间:2018-04-12 13:56:07

标签: perl hex pack

任何人都可以告诉我Perl中是否有任何函数将带符号的十进制转换为具有最小字节数的十六进制。

例如:-555(dec)=> FFFFFDD5(十六进制)[使用$ Hex = sprintf(“%X”, - 555)转换]

我希望结果是FDD5而不是FFFFFDD5。

2个字节足以表示-555。但我得到了4个字节的转换。

请帮忙!

2 个答案:

答案 0 :(得分:1)

您的方法存在两个问题。

  1. 它是不可解决的。

    例如,即使您只知道有两个数字, 字节12 34 56 78 16 可以指12 16 和345678 16
    字节12 34 56 78 16 可以指1234 16 和5678 16
    字节12 34 56 78 16 可以指123456 16 和78 16

    您可以使用一些外部方法来识别编码数字的长度,但这会使部分或全部节省无效。

  2. 这是不明确的。

    例如,
    字节FD D5 16 可以指64981 10 (字节00 00 FD D5 16 作为int32)。
    字节FD D5 16 可以指-555 10 (字节FF FF FD D5 16 作为int32)。

  3. 一种解决方案是使用长度前缀(如UTF-8)。

    -2^13..2^13-1  2 bytes  00xx xxxx  xxxx xxxx
    -2^21..2^21-1  3 bytes  01xx xxxx  xxxx xxxx  xxxx xxxx
    -2^29..2^29-1  4 bytes  10xx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx
    -2^31..2^31-1  5 bytes  1100 0000  xxxx xxxx  xxxx xxxx  xxxx xxxx  xxxx xxxx
    

    最佳方案取决于您的号码分布。

    上述方案的打包/编码功能可以写成如下:

    sub pack_vint32 {
       my $n = shift;
       my $nn = $n >= 0 ? $n : ~$n;
       return substr(pack('L>', ($n & 0x3FFF    ) | 0x0000    ), -2) if !($nn & ~0x1FFF);
       return substr(pack('L>', ($n & 0x3FFFFF  ) | 0x400000  ), -3) if !($nn & ~0x1FFFFF);
       return substr(pack('L>', ($n & 0x3FFFFFFF) | 0x80000000), -4) if !($nn & ~0x1FFFFFFF);
       return "\xC0".pack('L>', $n);
    }
    

    上述方案的解包/解码功能可以写成如下:

    sub unpack_vint32 {
       for (shift) {
          if (/^[\x00-\x3F]/) {
             return if length() < 2;
             my $n = unpack('L>', "\x00\x00".substr($_, 0, 2, '')) & 0x3FFF;
             $n -= 0x4000 if $n & 0x2000;
             return $n;
          }
          elsif (/^[\x40-\x7F]/) {
             return if length() < 3;
             my $n = unpack('L>', "\x00".substr($_, 0, 3, '')) & 0x3FFFFF;
             $n -= 0x400000 if $n & 0x200000;
             return $n;
          }
          elsif (/^[\x80-\xBF]/) {
             return if length() < 4;
             my $n = unpack('L>', substr($_, 0, 4, '')) & 0x3FFFFFFF;
             $n -= 0x40000000 if $n & 0x20000000;
             return $n;
          }
          elsif (/^\xC0/) {
             return if length() < 5;
             return unpack('xl>', substr($_, 0, 5, ''));
          }
          elsif (length() == 0) {
             return;
          }
       }
    
       croak("Bad data");
    }
    

    测试:

    my $s =
       join '',
          map { pack_vint32($_) }
             map { $_, -$_ }
                130, 555, 0x12, 0x345678, 0x12345678;
    
    say length($s);
    say sprintf("%v02X", $s);
    
    while ( my ($n) = unpack_vint32($s) ) {
       say $n;
    }
    
    croak("Bad data") if length($s);
    

    输出:

    28
    00.82.3F.7E.02.2B.3D.D5.00.12.3F.EE.80.34.56.78.BF.CB.A9.88.92.34.56.78.AD.CB.A9.88
    ----- ----- ----- ----- ----- ----- ----------- ----------- ----------- -----------
    130     |     |     |     |     |        |           |           |           |
    -130 ---+     |     |     |     |        |           |           |           |
    555 ----------+     |     |     |        |           |           |           |
    -555 ---------------+     |     |        |           |           |           |
    18 -----------------------+     |        |           |           |           |
    -18 ----------------------------+        |           |           |           |
    3430008 ---------------------------------+           |           |           |
    -3430008 --------------------------------------------+           |           |
    305419896 -------------------------------------------------------+           |
    -305419896 ------------------------------------------------------------------+
    

答案 1 :(得分:-1)

我认为这个问题想要十六进制的字符串(ASCII)表示,它采用二进制补码中的最小字节数。我同意评论和另一个关于这个含糊不清的答案,但它可能适合“人类”消费。在这种情况下:

sub min_bytes {
  my $n = shift;

  my $s = 256; # Fits in one byte
  my $i = 1;   # Bytes counter
  while( 1 ) {
    if( $n < 0 && -$n <= $s / 2 ) {
      return $i;
    }
    elsif( $n >= 0 && $n < ($s / 2) - 1 ) {
      return $i;
    }
    $s *= 256;
    $i++;
  }
}

sub to_hex {
  my $n = shift;

  my $l = min_bytes($n);
  my $h = $n > 0 ? $n : (256 ** $l) + $n;

  my $s = '';
  for( my $i = 0; $i < $l; $i++ ) {
    $s = unpack('H2', pack('C', $n % 256)) . $s;
    $n = $n >> 8;
  }

  return $s;
}

# Let's try a few numbers
my @numbers = (-10, -555, -100000, -100000000, -10000000000, -10000000000000);

for my $n (@numbers) {
  my $l = min_bytes($n);
  my $h = to_hex($n);
  print $n, " takes ", $l, " byte(s) and looks like ", uc $h, "\n";
}

我试过不要使用任何复杂的东西。位unpack('H2', pack('C', $n % 256))将单字节十进制数转换为十六进制数。

打印:

-10 takes 1 byte(s) and looks like F6
-555 takes 2 byte(s) and looks like FDD5
-100000 takes 3 byte(s) and looks like FE7960
-100000000 takes 4 byte(s) and looks like FA0A1F00
-10000000000 takes 5 byte(s) and looks like FDABF41C00
-10000000000000 takes 6 byte(s) and looks like F6E7B18D6000

代码使用Perl算法,对于大于内部表示精度的数字,可能会得到错误的结果。