thor has asked for the wisdom of the Perl Monks concerning the following question:

Greeting brethren/sisteren,
I have a problem that I'm trying to figure out, but I seem to be stumpped. First, the relevant code:
#!/usr/local/perl5.006/bin/perl -w use strict; my $var = shift; print "var: $var\n"; my $temp = sprintf "%lx", $var; print "temp: $temp\n"; my $packed = pack "H*", $temp; print "packed: $packed\n"; $temp = unpack "H*", $packed; print "temp: $temp\n"; my $unpacked = hex $temp; print "unpacked: $unpacked\n";
...and now the output:
>pack.pl 12 var: 12 temp: c packed: À temp: c0 unpacked: 192 >
I don't understand why when I unpack the variable $packed, it doesn't come back with what I packed it with. What I also find strange is that the behavior alternates depending on what range the input is in. Here's what I've found (x represents the input value):
16^0 <= x < 16^1 (# of hex digits is odd) bad 16^1 <= x < 16^2 (# of hex digits is even) good 16^2 <= x < 16^3 (# of hex digits is odd) bad 16^3 <= x < 16^4 (# of hex digits is even) good ...
Definately a pattern. The way I figure it, pack is packing this in to the appropriate number of bytes, and when it doesn't have enough to fill an entire byte, it 0 pads to the right. Don't know how to exploit that, however (other than a hack with logarithms to find out what range it's in and act accordingly)

any help would be appreciated,
thor

Replies are listed 'Best First'.
(jeffa) Re: Pack/unpack irregularity
by jeffa (Bishop) on May 30, 2002 at 04:38 UTC
    Updated node - original 'answer' replaced with explaination.

    I originally thought you could get away with using "h*" instead of "H*", but jsprat is correct - it is the "%2x" that matters. So, why?

    When you format the number 1 with "%1x" and pack it, you get the hexidecimal number 10 (binary number '00010000') when you format it with "%2x" and pack it, you get the hexidecimal number 1 (binary number '00000001'):

            %1x         %2x
    1    0001 0000   0000 0001
    2    0010 0000   0000 0010
    3    0011 0000   0000 0011
    
    So, 1 becomes hex 10, 2 becomes hex 20, 3 becomes hex 30, and so on. Once we hit 16, however, we get the results we expect. The problem lies in the formatted 'string' we are packing. When you format the number with "%2x", you get a leading space added for any decimal number less than 16, AKA hexidecimal numbers that only use 1 digit:
         %1x  %2x
    1    '1'  ' 1'
    2    '2'  ' 2'
    
    Now, try these one-liners:
    perl -le 'print unpack("H*",pack("H*",1))' perl -le 'print unpack("H*",pack("H*"," 1"))' perl -le 'print unpack("H*",pack("H*","01"))'
    See the difference? I believe that unpack is expecting at least two digits, so it adds a zero for you - in the wrong place. The moral is to pad your hex numbers with a leading 0 if they are less then 2 digits.

    Now, there is more than one way to skin this cat. Don't be tempted to think that pack is the only culprit - unpack will throw a monkey wrench in the system as well. For example, consider this one-liner that works for one-digit hex numbers:

    perl -le 'print unpack("h*",pack("H*","1"))'
    Are we having fun yet? ;)

    Also note that you will need to use more than two nibbles for values over 255. Formatting with "%4x" will handle up to 65535, AKA (2^16) - 1.

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      Just noting that the original poster used "%lx" (lower case "l") not "%1x" (number one)... your explanation is still good though - sprintf would have to default to 1 - defaulting to 0 would be bad! :)

      --
      my $chainsaw = 'Perl';

Re: Pack/unpack irregularity
by jsprat (Curate) on May 30, 2002 at 00:47 UTC
    It doesn't make much sense to me either. I can suggest a workaround, however, and leave the technical explanation to someone who understands it better than I do.

    Instead of
    sprintf "%1x", $var;
    use
    sprintf "%2x", $var;

    This will give two nybbles (a full byte) so pack and unpack give the expected answer.

    Update: Changed bytes to nybbles - Doh!

    Update 2: ++ greenFox for pointing out the why.

      from the pack man page- "With all types except "a", "A", "b", "B", "h", "H", and "P" the pack function will gobble up that many values from the LIST, <snip>The "h" and "H" fields pack a string that many nybbles long."

      --
      my $chainsaw = 'Perl';

        Good call! Now, if I can get the darned thing to right justify, I'm set, 'cuz a leading zero should cause no problems at all (crosses fingers and hopes that that is true...)

        thanks,thor

Re: Pack/unpack irregularity
by rbc (Curate) on May 29, 2002 at 23:58 UTC
    I am guessing that you expect to see ...
    unpacked: 12

    as output. If my guess is right then you need to use ...
    "H"
    ... instead of ...
    "H*"
    ... in your pack and unpack calls.
      This would work like a charm, except that my data is more than likely not going to be less than 16. With the changes you suggested, here is the output:
      >pack.pl 17 var: 17 temp: 11 packed: temp: 1 unpacked: 1 >

      thanks anyway,
      thor