http://qs1969.pair.com?node_id=1225073

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

Howdy Monks! I've got a question about what goes on behind the scenes when the function unpack is called. I'm trying to understand the results of a certain function for a script I inherited, but I can't figure out how the result is being calculated by Perl. See below for a snippet of the function.

my $input = 0x916CD; #595661 my $unpacked = unpack "V", $input; print($unpacked); >>909457717 (0x36353935)

I just can't seem to figure out, by hand, how Perl is unpacking the decimal value into that number. Can someone shed some light on what goes on behind the scenes in the unpack function that might be able to help me understand? Thanks!

Replies are listed 'Best First'.
Re: Understanding Unpack on Decimal Value
by choroba (Cardinal) on Nov 01, 2018 at 21:05 UTC
    First, check pack:

    > V An unsigned long (32-bit) in "VAX" (little-endian) order.

    Have you noticed the pattern?

    bytes chars 36353935 ----> 36 35 39 35 ----> 6 5 9 5 | | | | first 4 digits reverse | | | | 595661 -------------> 5956 ------> 6 5 9 5

    It works for any input greater or equal to 1000:

    #! /usr/bin/perl use warnings; use strict; use Test::More; sub what { my ($input) = @_; my $unpacked = unpack "V", $input; my $result = join "", map chr hex, sprintf("%x\n", $unpacked) =~ / +(..)/g; is $result, reverse substr $input, 0, 4; } what($_) for 1000 .. 2000, 2e4, 3e5, 4e6, 5e7, 6e8, 7e9, 8e10, 9e11, 2 ** 64 - 3; done_testing();

    Update: For numbers fitting into integer, you can also use

    reverse($input) % 10_000

    ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

      ASCII!! That didn't even occur to me as this portion of the function is entirely numeric. I suspect that the way this was implemented in the original script was incorrectly understood. Thank you for the clarification. One additional question, if I could...

      Since it returns only a portion of the result, I attempted to modify the snippet a little bit, but it still produced the same result. See below.

      my $input = 0x916CD; #595661 my @unpacked = unpack "VV", $input; print(join ", ", @unpacked); >>909457717 #0x36353935

      From your reply, I was expecting to have the following.

      Dec: 825_636_405, 14_645 Hex: 0x31363635, 0x3935

      Could you help me understand why this doesn't unpack into the array like I thought it would? Thanks for the help!

        You need a number greater than 9_999_999 to get more than one element back:
        for my $input (9_999_990 .. 10_000_010) { my @unpacked = unpack 'VV', $input; say scalar @unpacked, " $input"; }
        ($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,
Re: Understanding Unpack on Decimal Value
by BillKSmith (Monsignor) on Nov 03, 2018 at 13:34 UTC
    Much of the strangeness that choroba describes has nothing to do with unpack. Unpack requires a string input. Your input is a number. When perl needs a number in the form of a string, it converts it much the way print would. Your "V" tells unpack to process the first four characters of that string. I suspect that your original programmer meant to use pack rather than unpack.
    Bill