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

So, I've finally gotten around to working with bitwise operators, and I'm a bit stuck.

What I want to do is have a pre-configured byte, then reconfigure specific bits within it depending on a parameter to a function. Say I have a byte set to 128 (10000000), and want to flip the last two bits. That's easy with an OR, as the least significant bits are already 0.

However, say a function receives a parameter that says to change that 3 (last two bits) to a two instead (unset the right-most bit to 0). I can easily do that as well:

use warnings; use strict; my $base = 128; # OR to get 131 (10000011) $base = $base | 3; # combine NOT and AND and unset the last bit $base = $base &= ~(1 << 1);

What I'm wondering is if there's a way to change several bits at a time. Instead of the last bit, what if I wanted to go from 10001010 to say, 10001101? (from 10 to 13 in the last four bits). Is there an easy way to do that, or does each bit have to be switched one at a time?

I suppose I could just subtract the initial number then add in the new value, but that kind of defeats the purpose of me learning this. Am I thinking about this all wrong?

Replies are listed 'Best First'.
Re: Modifying multiple bits in a byte - bitwise operators
by BrowserUk (Patriarch) on Jan 10, 2017 at 00:46 UTC
    What I'm wondering is if there's a way to change several bits at a time. Instead of the last bit, what if I wanted to go from 10001010 to say, 10001101? (from 10 to 13 in the last four bits). Is there an easy way to do that

    If you know the input is going to be (or: when it is) 10001010, that you want to change it to 10001101; simply assign the latter value to it.

    Another interpretation of your requirement is that given ?????010 you want to change it to ?????101; then:

    sub changeIt { my $old = shift; return ( $old & 0b11111000 ) | 0b00000101; }

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    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". The enemy of (IT) success is complexity.
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Modifying multiple bits in a byte - bitwise operators (xor)
by Anonymous Monk on Jan 10, 2017 at 00:50 UTC

    So flip the bits? Thats xor/xor

    10001010 10001101 +-+

    So you write

    use Data::Dump; $f = 'Q'; $g = "\261"; $r = $f ^ $g; $t = $f ^ $r; dd({$_, unpack('b*', $_)}) foreach ($f, $g, $r, $t); __END__ { Q => 10001010 } { "\xB1" => 10001101 } { "\xE0" => "00000111" } { "\xB1" => 10001101 }
    To turn $f into $g, you take the the xor of $f and $g to create $r, and then xor $f with $r to get $g
Re: Modifying multiple bits in a byte - bitwise operators
by AnomalousMonk (Archbishop) on Jan 10, 2017 at 00:50 UTC

    Another way:

    c:\@Work\Perl\monks>perl -wMstrict -le "my $x = 0b10001010; my $target = 0b10001101; ;; my $mask = $x ^ $target; ;; my $result = $x ^ $mask; printf qq{0b%b \n}, $result; ;; die qq{target $target != result $result} if $target != $result; " 0b10001101


    Give a man a fish:  <%-{-{-{-<

Re: Modifying multiple bits in a byte - bitwise operators
by johngg (Canon) on Jan 10, 2017 at 16:43 UTC

    If you want work with a byte to contain your values in the range 0 - 255 then you should use pack rather than just assigning the value. Here's a script that uses subroutines to flip, set or unset bits within a byte, bits range from 7 as most significant to 0 as least. Each routine uses vec to create a mask of the bits to be manipulated and displays the mask to show what is going on.

    use strict; use warnings; use feature qw{say}; my $base = 128; say q{Non-packed base - }, unpack q{B*}, $base; say q{}; $base = pack q{C}, 128; say q{Packed base - }, unpack q{B*}, $base; my $three = pack q{C}, 3; say q{Three for or - }, unpack q{B*}, $three; $base |= $three; say q{Base now - }, unpack q{B*}, $base; say q{}; $base = pack q{C}, 138; say q{Revised base 138 - }, unpack q{B*}, $base; flipBits( \ $base, 0 .. 2 ); say q{Flipped base 141 - }, unpack q{B*}, $base; say q{}; unsetBits( \ $base, 0 .. 7 ); say q{Cleared base - }, unpack q{B*}, $base; say q{}; setBits( \ $base, 7 ); say q{Set high bit - }, unpack q{B*}, $base; say q{}; setBits( \ $base, 0, 1 ); say q{Set two lowest - }, unpack q{B*}, $base; say q{}; flipBits( \ $base, 0 .. 3 ); say q{Flip four lowest - }, unpack q{B*}, $base; say q{}; setBits( \ $base, 0 .. 2 ); say q{Set three lowest - }, unpack q{B*}, $base; say q{}; sub flipBits { my( $rsByte, @bits ) = @_; my $vec; vec( $vec, $_, 1 ) = 1 for @bits; say q{Flip mask - }, unpack q{B*}, $vec; ${ $rsByte } = ${ $rsByte } ^ $vec; } sub setBits { my( $rsByte, @bits ) = @_; my $vec; vec( $vec, $_, 1 ) = 1 for @bits; say q{Set mask - }, unpack q{B*}, $vec; ${ $rsByte } = ${ $rsByte } | $vec; } sub unsetBits { my( $rsByte, @bits ) = @_; my $vec; vec( $vec, $_, 1 ) = 1 for @bits; say q{Unset mask - }, unpack q{B*}, $vec; ${ $rsByte } = ${ $rsByte } & ( ~ $vec ); }

    The output.

    Non-packed base - 001100010011001000111000 Packed base - 10000000 Three for or - 00000011 Base now - 10000011 Revised base 138 - 10001010 Flip mask - 00000111 Flipped base 141 - 10001101 Unset mask - 11111111 Cleared base - 00000000 Set mask - 10000000 Set high bit - 10000000 Set mask - 00000011 Set two lowest - 10000011 Flip mask - 00001111 Flip four lowest - 10001100 Set mask - 00000111 Set three lowest - 10001111

    I hope this is helpful.

    Cheers,

    JohnGG

Re: Modifying multiple bits in a byte - bitwise operators
by Lotus1 (Vicar) on Jan 10, 2017 at 20:52 UTC
    #!/usr/bin/perl use warnings; use strict; my $mask1 = 0b1101_1101; my $mask2 = 0b0010_0010; printf " \$mask1 = %b\n", $mask1; #Use bitwise NOT to flip all the bits. printf "~\$mask1 = %b\n\n", ~$mask1; #use bitwise OR to set bit(s) but keep original values for all other b +its. my $value = 0b1010_1000; printf " %b \$value\n| %b \$mask1\n----------\n %b\n\n\n", $value, $ +mask1, $value | $mask1; printf " %b \$value\n| %08b \$mask2\n----------\n %b\n\n\n", $value, + $mask2, $value | $mask2; #use bitwise AND to clear bit(s) but keep original values for all othe +r bits. printf " %b \$value\n& %b \$mask1\n----------\n %b\n\n\n", $value, $ +mask1, $value & $mask1; printf " %b \$value\n& %08b \$mask2\n----------\n %08b\n", $value, $ +mask2, $value & $mask2; #If you just want to test the value of a bit then clearing the other b +its # lets you test the value for the remaining one. __END__ $mask1 = 11011101 ~$mask1 = 111111111111111111111111111111111111111111111111111111110010 +0010 10101000 $value | 11011101 $mask1 ---------- 11111101 10101000 $value | 00100010 $mask2 ---------- 10101010 10101000 $value & 11011101 $mask1 ---------- 10001000 10101000 $value & 00100010 $mask2 ---------- 00100000
Re: Modifying multiple bits in a byte - bitwise operators
by stevieb (Canon) on Jan 10, 2017 at 17:01 UTC

    Thank you very much for the replies all. I tested them all out and am still confused about certain things, so I will definitely be doing some major reading/testing this week. I've posted this here just for completeness.

    What I did find out is that I can unset all bits at a certain location by doing a negate AND using the total value of the bits I want to unset. For example, if I want to unset bits 4-3:

    my $x = 0xFF; # 0x18 == 24, the max value of both bits # 4-3 set printf("%b\n", &= ~0x18); __END__ 11100111

    Then, I can re-set those bits by ORing the bit string with any value within the value range of those two bytes:

    $x |= 0x14; # 20; __END__ 11110111

    Here's my first working sample of real-world code that I've put together. I'm certain it isn't the most concise, efficient or elegant code, but it'll get better as I learn about how all the bitwise operators work. In my case, I'm dealing with a 16-bit wide configuration register that are stored as two bytes, but when working on them, I merge them together so the code more closely represents what the hardware's datasheet specifies as bit locations:

    sub queue { my ($self, $q) = @_; if (defined $q){ if (! exists $queue{$q}){ die "queue param requires a binary string.\n"; } $self->{queue} = $queue{$q}; } $self->{queue} = DEFAULT_QUEUE if ! defined $self->{queue}; my $bits = $self->bits; # unset $bits &= ~MAX_QUEUE; # set $bits |= $self->{queue}; my $lsb = $bits & 0xFF; my $msb = $bits >> 8; $self->register($msb, $lsb); return $self->{queue}; }
      Hi stevieb!

      Yes, the generic set of HLL operations to set a field of bits is to do a bit-wise AND with a one's complement of a "mask" to make sure the bits are all unset, then do a bit-wise OR operation to set those bits to however you want them.

      The difference between the HLL version and an actual Assembly language implementation is that there is some uncertainty in how big a "natural integer" is with the HLL. With assembly, I know exactly whether I've got 8, 16, 32, or 64 bits. Typically with HLL, you only know something like size >= 32 bits for an int.

      So, with ASM, the "negate the MASK" step would not be there. The ~MASK value would have been compiled in as a "load immediate" constant. With HLL, we want to actually calculate this "flipping of bits" mask. When we calculate ~0x3, we get 2 bits of zeroes and as as many leading one bits as needed to make up the "native int". Design for a min size int, but allow bigger ints.

      Here is some code with a couple of things I like to do:

      use strict; use warnings; use constant {UP => 0x0<<3, DOWN => 0x1<<3, LEFT => 0x3<<3, RIGHT => 0x2<<3, MASK => 0x3<<3}; printf "direction Mask: "; print8bits (MASK); #Prints: direction Mask: 0001 1000 my $register8b=0; #set direction left $register8b = $register8b & ~MASK | LEFT; print8bits ($register8b); #Prints: 0001 1000 #set direction down $register8b = $register8b & ~MASK | DOWN; print8bits ($register8b); #Prints: 0000 1000 sub print8bits { my $int = shift; print map{s/(\d{4})/$1 /g;$_;}sprintf "%08b\n", $int; }
      1. Consider the use of the constant feature. Make a constant block for each of the fields in the 16 bit register. Naming convention to your satisfation. Maybe DIRECT_UP or DIRECTION_UP instead of just UP or whatever. Consider the naming convention in whatever documentation that you are working from.
      2. I prefer "0x3" notation over just decimal 3 for these things. Although they code to exactly the same bits, use of 0x notation alerts me to the fact that this is some hardware bit thing and not a "normal" variable.
      3. For debugging, I find something like my sub print8bits() to be useful. Space the bits out into 4 bit hex compatible groups. The %b format spec in Perl is very nice (doesn't exist in std C.. have to "roll your own").
      4. Consider a naming convention to indicate how many of the bits are "significant". $register8bit , $r8bit_control or whatever. Design the code that it will still work with native int sizes greater than what current platform is.
      5. While manipulating the 16 or 8 bit register, I just keep the internal representation as a native int, whatever that size is, rather than splitting this into say $lsB and $msB like you did. Or mucking around prematurely with pack/unpack. BTW, your code could confuse a hardware guy. Use lower case "b" for bit and upper case "B" for byte, e.g. $msB instead of $msb.
      6. If you do a lot of bit twiddling, you will find that the inability to access the hardware carry bit is a real hassle. There are various techniques around that, but not in the scope of this post.
      Hope this helps.
      I have written most of my bit fiddling code in C as I am emulating say a hardware ECC algorithm or whatever and sometimes "fast" is important. But Perl can do bit manipulation amazingly well and I have used Perl to decode some complex binary formats.

        This is a great breakdown and explanation. Thanks Marshall!