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

I'm trying to get my head around some (what I think are) basic bitfield manipulations using either vec() or un/pack... but I've not used these functions much and I'm going around in circles.

I have a single 8-bit byte where each bit represents whether a directly-mapped item is 'included' or not. An example:

$actions = " ABCDEFG"; $item = 0x37; # ...or 00110111b $newitem = &SomeMagic($item); # ...giving " BC EFG"
I would think it's something pretty simple but I haven't been able to nut it out or find anything like it in any of my books or any net searches.

Any suggestions?

Thanks.

Replies are listed 'Best First'.
Re: Decoding Mapped Bitfields
by Zaxo (Archbishop) on Oct 09, 2005 at 12:01 UTC

    I think vec is all you need for this. The $actions reference string can be treated as a vector of eight byte-sized chunks, and the $item character as a vector of eight bits. That will let each share an index for the correspondence you want (though it's necessary to be tricky to get the bit order you wanted).

    our $actions = " ABCDEFG"; my $item = chr(0x37); my $newitem = join '', map { vec($item, 7-$_, 1) ? chr(vec $actions, $_, 8) : ' '; } 0 .. 7; print $newitem,$/; __END__ BC EFG
    It may have been the need for the chr functions that was troubling you. Your $item was actually an integer.

    Occasionally we hear from C programmers learning Perl that they really must have strings as an array of characters. Vec is the answer to that plea (as is substr). It's just awkward enough to move them to find a perlier way ;-)

    After Compline,
    Zaxo

      Using chr(vec $actions, $_, 8) as a substitute for substr($actions, $_, 1) might confuse the issue here. The first use of vec() is the important one. I think you hit it exactly, though. He needed chr().

      -sauoq
      "My two cents aren't worth a dime.";
      
Re: Decoding Mapped Bitfields
by sauoq (Abbot) on Oct 09, 2005 at 12:28 UTC
    I have a single 8-bit byte ...
    $item = 0x37; # ...or 00110111b

    If I had to guess about what's ruining your day when you try to use vec() (and I do because you haven't shown us your code) I'd say you are getting confused by the way Perl handles numbers. Perl isn't C and $item = 0x37; is not a single byte like char item = 0x37; would be in C. It's two bytes: "55". To get a single byte use chr() or a string containing a single byte (like "\x37") instead.

    $ perl -le 'print vec(0x37, $_, 1) for 0..7' # Ooops!!! $ perl -le 'print vec(chr(\x37), $_, 1) for 0..7' # Much better. $ perl -le 'print vec("\x37", $_, 1) for 0..7' # This works too.

    Using unpack you have to jump through a few more hoops to get the same thing. You can unpack with a template of "b*" (or "B*" for reverse bit order) but that then gives you a string of 1s and 0s. You can unpack that with "c*" to get one value per bit... but the values won't be what you expect. Instead of "0"s and "1"s, you'll get "48"s and "49"s (i.e. ord("0") and ord("1").) So, to get the actual "0"s and "1"s you want, you have to then use chr(). Putting it all together gives something like this:

    $ perl -le 'print for map chr, unpack "c*", unpack "b*", chr(0x37)'
    Which gives the same output as the correct versions using vec() above. As you can see, this is exactly the kind of thing vec() was made for. You can make unpack do it, but it's not exactly the right tool for the job.

    -sauoq
    "My two cents aren't worth a dime.";
    
      Ok, that was my downfall -- the chr() to turn a 16-bit int (under Win32 Perl) into a 'real' 8-bit byte.

      I did initially look at my C books as well as my Perl books to come up with the idea of using a bitfield in my application anyway. Just that I wasn't sure how to deal with it other than through the substr() approach (I've not really used vec() much, if at all, really).

      Still, I knew there had to be a simpler way...

      Many thanks for all your help, folks... 'tis greatly appreciated.

Re: Decoding Mapped Bitfields
by BerntB (Deacon) on Oct 09, 2005 at 11:18 UTC
    This is how I usually do it. But I am C-damaged and also wonder how you do it with maps etc.

    my($actions) = " ABCDEFG"; my($bits) = 0x37; my($bplace) = 1; my($index) = length($actions); while(--$index >= 0) { substr($actions, $index, 1, " ") if !($bplace & $bits); $bplace += $bplace; } print $actions, "\n";