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

I've never groked converting bit vectors, bitmaps, packed fields or any Perl bitwise operations and it's high time I do. I want to learn something using a practical example of my own that I kind of understand already rather than trying to puzzle through unfamiliar examples in a book, manpage or tutorial.

Let's say I have these two bytes in a variable: 0xA8 0x38. ("Are these two bytes a string or a 16-bit integer?" you ask. I don't know; you tell me. This is part and parcel of my profound ignorance of this stuff. Pretend I just read the two bytes from a file into a variable.) These 16 bits are a packed representation of an MS-DOS FAT date.

I want to use Perl bitwise operations to convert this 16-bit integer (or string) to the date that it represents: 2008-05-08. Here's the conversion as I (naïvely) understand it:

A8 38 Hexadecimal (Little Endian) 10101000 00111000 Binary (Little Endian) 38 A8 Hexadecimal (Big Endian) 00111000 10101000 Binary (Big Endian) 11111100 00000000 54321098 76543210 Bit Ruler (Indexed From 0) 0011100 0101 01000 Binary (Regrouped) 1111110 0000 00000 5432109 8765 43210 Bit Ruler (Regrouped) Year Month Day Packed Format 0011100 0101 01000 Binary 1C 05 08 Hexadecimal 28 5 8 Decimal Year = 1980 + 28 = 2008 Month = 5 Day = 8 MS-DOS FAT Date = 2008-05-08
How do I accomplish this conversion in Perl? All hints are welcome. Remember: I'm seeking genuine learning more than a solution to a problem -- and especially not the answer to a homework problem, I promise. IANAS!

Jim

Replies are listed 'Best First'.
Re: Practical Example of Converting Packed Value
by BrowserUk (Patriarch) on Dec 01, 2008 at 00:16 UTC

    I'd probably use just bitwise math, but as you've asked for pack, here's one way:

    $s = chr( 0xa8 ) . chr( 0x38 );; print unpack 'b*', $s;; 0001010100011100 print unpack 'A5A4A7', unpack 'b*', $s;; 00010 1010 0011100 print map{ unpack 'C', pack 'b*', $_ } unpack 'A5A4A7', unpack 'b*', $ +s;; 8 5 28 ( $day, $month, $year ) = map{ unpack 'C', pack 'b*', $_ } unpack 'A5A4A7', unpack 'b*', $ printf "%4d-%02d-%02d\n", 1980+$year, $month, $day;; 2008-05-08

    Updated: Or using bitwise math:

    $s = chr( 0xa8 ) . chr( 0x38 );; $n = unpack 's', $s;; ( $day, $month, $year ) = ( $n & 0x1f, ( $n & 0x1e0 ) >> 5, ( $n & 0xfe00 ) >> 9 );; printf "%4d-%02d-%02d\n", 1980+$year, $month, $day;; 2008-05-08

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Thank you, ++BrowserUk!

      I actually didn't ask for solutions using pack/unpack; I only described the representation format as "packed". Please feel free to show me how you would do it using bitwise math.

      Jim

        Please feel free to show me how you would do it using bitwise math.

        That's what the second snippet does. First, unpack 's' is used to convert the bytes representing a number into something Perl understands to be a number. Then bit math is performed to extract each of the fields.