Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411581 Posts in 69386 Topics- by 58445 Members - Latest Member: Mansreign

May 05, 2024, 07:33:39 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Bitwise Operations in C++
Pages: [1]
Print
Author Topic: Bitwise Operations in C++  (Read 4383 times)
dspencer
Level 3
***


View Profile WWW
« on: August 02, 2009, 06:15:56 PM »

Hi!

I'm at a point where I think it would be useful to use bitwise operations in my code, for my game (I want a bunch of boolean flags, so its either this or a class with just a bunch of boolean members). Problem is, despite me understanding the concepts behind it, I have no idea how to actually do a bitwise operation. I'm using c++. Can anyone suggest a tutorial, or give me a quick rundown on how to do it? I'm not even sure what datatype to use... is it just ints?

Thanks in advance!
Logged

Overkill
Level 3
***


Andrew G. Crowell


View Profile WWW
« Reply #1 on: August 02, 2009, 06:50:28 PM »

Bitfields are just basically integer types, yeah. Bitwise operators let you treat each bit of the number separately, since all types are internally represented binary numbers.

On 32-bit machines, you'd be able to pack 32 boolean flags into a single int. And 64-bit allow 64 separate boolean flags. Well, sort of. There's a few details you'll need to worry about.

(Technical sidenote! It all depends on your compiler and platform, since the standards only require minimum capacities for each primitive. A lot of the time though, an int is exactly the word size of the CPU you're using. You might want to get some typedefs going to ensure the proper bit length of your bitfield. So something like u8 for unsigned 8-bit, u32 for unsigned 32-bit. There are plenty of headers you can find that typedef to proper sizes for you. Since this is C++ you mentioned, there is a chance your particular compiler might not obey C99 standards, so it might not have uint32_t. But with enough conditionally-compiled typedefs, you can get something! Or you can just assume an int is 32-bit, which it probably will be on your machine anyways.)

Each bit corresponds to a power of two in binary. So your individual bits are addressed by: 1, 2, 4, 8, 16, 32, 64, and so on. Or to save time writing, use hexadecimal: 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80, 0x100, 0x200, ...

And if you're using a 32-bit unsigned int, the highest bit index is 2**31 (2,147,483,648 or 0x80000000).

When all bits are off, you have a value of 0 in your bitfield.
When all bits are on, you get a value of 2**31-1 (4,294,967,295 or 0xFFFFFFFF).

Let's start by declaring an int we intend to manipulate with bitwise operators. Make sure it's unsigned, or else you have a sign-bit to account for too.
Code:
unsigned int b = 0;

If you want to "turn on" a bit in a bitfield, you use a bitwise OR (|).
Code:
b |= 1;
b |= 3; // Turns on bits 1 and 2 at the same time.

To "toggle" a bit, use bitwise exclusive or / XOR (^)
Code:
b ^= 4; // Turns the bit on!
b ^= 4; // Turns it off.

To "test" if a bit is on, use bitwise AND (&)
Code:
//Check if bit at 2 is on.
if (b & 2 == 2)
{
     // The bits were on.
}

To "invert" the bits of a number, use bitwise NOT (~).
Code:
b = ~b;

To "turn off" a bit, you use bitwise and combined with bitwise not, also called NAND (&~).
Code:
b &= ~2;

To "shift" the bits left, or right, use << and >>.
This is equivalent to multiplying a number by (2**n) when shifting left.
and is equivalent to dividing a number by (2**n) when shifting right.
Bitshifts are kind of complicated, but they are very handy for fixed-point math.
Code:
b <<= 2; // Multiply by 4, or... move the bits left two digits.
b >>= 3; // Divide by 8, or move the bits right three digits.

There are more tricks, too. But this should be enough to get you started.

That said, I'd still recommend a bunch of separate booleans, since it's more trouble than it's worth. That said, it does yield performance gains.

EDIT: some modifications due to a few things that weren't quite true in my explanation. There are plenty of nitpicky details, due to the fact this is low-level stuff. So be careful. Thanks, everyone!
« Last Edit: August 02, 2009, 09:30:34 PM by Overkill » Logged

Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #2 on: August 02, 2009, 07:34:22 PM »

Bitfields are just ints, yeah. And bitwise operators let you treat each bit of an int (since ints are internally represented binary numbers) separate.

Bit operators can be applied to any scalar type, not just ints.  They work on char, short, long, and long long too.

And then there are actual bitfields, which you don't see too often:

Code:
struct BitField
{
    unsigned flag_1 : 1;
    unsigned flag_2 : 1;
    unsigned flag_3 : 1;
};
Logged



What would John Carmack do?
John Nesky
Level 10
*****


aka shaktool


View Profile WWW
« Reply #3 on: August 02, 2009, 07:36:04 PM »

And if you're using a 32-bit unsigned int, the highest bit index is 2**31 (2,147,483,648 or 0x80000000). When all bits are off, you have a value of 0 in your bitfield. When all bits are on, you get a value of 2**31-1.
Correction: when all the bits are on, you have 2^32 - 1 == 0xFFFFFFFF.
Logged
Overkill
Level 3
***


Andrew G. Crowell


View Profile WWW
« Reply #4 on: August 02, 2009, 07:43:27 PM »

Average Software: I meant ints as in "integral" types, I just was trying to keep my explanation simple to read. Yeah, it works on other integral types too, of course. Since the only difference between those different types you listed are their size in bits.

And of course, you can't even really assume that an int is 32-bits across compilers, according to the standards, but I was trying to keep that mess away.

Definitely good to clarify that though, yeah.

I have yet to try aligned bitfield structs, to be honest. :D

And if you're using a 32-bit unsigned int, the highest bit index is 2**31 (2,147,483,648 or 0x80000000). When all bits are off, you have a value of 0 in your bitfield. When all bits are on, you get a value of 2**31-1.
Correction: when all the bits are on, you have 2^32 - 1 == 0xFFFFFFFF.

Wait, er. I'm confused. What are you correcting me on? I think we agree about this Smiley

I even said "When all bits are on, you get a value of 2**31-1." How is that different from "when all the bits are on, you have 2^32 - 1 == 0xFFFFFFFF." Other than, writing out the hex form, and using ^ for exponentation, which I avoided to not confuse with XOR.

Maybe I should clarify? Since it's possible my writing is sort of jumpy, sorry :/

When I said the "highest bit", this is not the same thing as "all the bits". It's the highest, single bit addressable. So the highest bit for an n-bit bitfield is: 2**(n-1).

Meanwhile, when all bits for an n-bitfield are on, you get a bigger number: 2**(n-1) + 2**(n-2) + 2**(n-3) + 2**(n-4) + ... + 2**3 + 2**2 + 2**1 + 2**0 = 2**n - 1.

Anyways.

EDIT: Updated my other post somewhat. Hopefully more clear. I will keep this post though since it explains some more technical things.
« Last Edit: August 02, 2009, 08:24:16 PM by Overkill » Logged

mewse
Level 6
*



View Profile WWW
« Reply #5 on: August 02, 2009, 08:07:57 PM »

On 32-bit machines, you'd be able to pack 32 boolean flags into a single int. And 64-bit allow 64 separate boolean flags.

Just as a quick and extremely pedantic point...  (Gosh, this is probably another post which really belongs in the "Grumpy Old Programmers" thread instead of here.  Sorry about that, in advance.)

The C/C++ standards don't specify the length of an 'int'.  Yes, on a 32-bit machine (whatever that means these days;  most modern CPUs have at least two different sizes amongst their various registers, and the width of their BUS often no longer matches either of them, etc. etc.  Microsoft seems to like to use "64-bit" vs. "32-bit" terms based upon the size of a pointer, which is even yet another possible criteria, and probably the one that was intended here)...  on a 32-bit machine, it's likely that an int will be 32 bits long, sure.  But it's not guaranteed;  all that the standards say is that an int won't be any shorter than a short, and no longer than a long, but not how big any of those other variables will be. 

So if you're planning to write code that will be compiled to run on other systems (Linux, Mac, BSD, PS2, Gameboy, etc), and you need a variable with a particular minimum number of bits in it, you should really find a variable type which guarantees that you'll get the size you want, such as 'uint32_t' (typically defined in <stdint.h>), which is guaranteed to be a 32-bit unsigned integer, regardless of what computer or toolchain you're using.

Note that sized integers (such as uint32_t) are only mandated as part of the C99 C standard, which means that they don't technically apply to C++ code.. however, I've never actually seen a C++ compiler that couldn't understand the C standards, where those standards didn't conflict with C++ ones.

And now I'll return to the grumpy old programmers thread.  Hey you kids, stay off my lawn!   Gentleman
« Last Edit: August 02, 2009, 08:13:38 PM by mewse » Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #6 on: August 02, 2009, 08:13:15 PM »

But it's not guaranteed;  all that the standards say is that an int won't be any shorter than a short, and no longer than a long, but not how big any of those other variables will be. 

You probably already knew this, but the standard does specify the size of char, it will always be 1 byte.  Of course, it doesn't specify how many bits are in a byte (not all systems use 8 bit bytes), so you're screwed anyway.
Logged



What would John Carmack do?
mewse
Level 6
*



View Profile WWW
« Reply #7 on: August 02, 2009, 08:24:20 PM »

But it's not guaranteed;  all that the standards say is that an int won't be any shorter than a short, and no longer than a long, but not how big any of those other variables will be. 

You probably already knew this, but the standard does specify the size of char, it will always be 1 byte.  Of course, it doesn't specify how many bits are in a byte (not all systems use 8 bit bytes), so you're screwed anyway.

Yup, true.  Though all modern systems I'm aware of thankfully use 8 bit bytes, so we can mostly ignore that possibility these days, except when working on ancient mainframes.  Smiley

But back in those bad old days, the individual bits in a byte were often stored in different directions (or even occasionally were given bizarre non-linear orderings!) on different computers, just to make things even more painful.  Thankfully, some measure of sanity finally prevailed;  the only remnants of those days still with us now is little-endian vs. big-endian byte ordering.  Everyone has (thankfully!) settled on big-endian ordering of bits.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #8 on: August 03, 2009, 05:09:17 AM »

Everyone has (thankfully!) settled on big-endian ordering of bits.

Not remotely.  Last time I checked, almost everything outside of PCs is little-endian.
Logged



What would John Carmack do?
mirosurabu
Guest
« Reply #9 on: August 03, 2009, 06:12:20 AM »

What you're trying to do is called bit-packing and I suggest you not to do that. Is there any good reason to do that? It sounds like premature optimization. Instead, use an array or even better, given that you are using C++, use an STL vector or map.
Code:
std::vector<boolean> flags;

Bitwise operations are good to know but that does not mean you have to use them.
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #10 on: August 03, 2009, 06:56:43 AM »

Everyone has (thankfully!) settled on big-endian ordering of bits.

Not remotely.  Last time I checked, almost everything outside of PCs is little-endian.

Gah, I screwed this up again. x86 chips are little-endian, the rest of the world (game consoles, cell phones, basically anything embedded) is big-endian.
Logged



What would John Carmack do?
Glaiel-Gamer
Guest
« Reply #11 on: August 03, 2009, 07:10:45 AM »

Everyone has (thankfully!) settled on big-endian ordering of bits.

Not remotely.  Last time I checked, almost everything outside of PCs is little-endian.

Gah, I screwed this up again. x86 chips are little-endian, the rest of the world (game consoles, cell phones, basically anything embedded) is big-endian.

he said ordering of bits, not bytes
Logged
Average Software
Level 10
*****

Fleeing all W'rkncacnter


View Profile WWW
« Reply #12 on: August 03, 2009, 07:30:52 AM »

Everyone has (thankfully!) settled on big-endian ordering of bits.

Not remotely.  Last time I checked, almost everything outside of PCs is little-endian.

Gah, I screwed this up again. x86 chips are little-endian, the rest of the world (game consoles, cell phones, basically anything embedded) is big-endian.

he said ordering of bits, not bytes

Indeed, a thousand pardons.
Logged



What would John Carmack do?
Cymon
Level 9
****


Computer Kid


View Profile WWW
« Reply #13 on: August 04, 2009, 01:50:24 PM »

I like bitwise operations. So many coolness options, and once you get them they're awesome.

Here's a game I wrote that uses some bitwise operations:
http://cymonsgames.com/buttons/
Because I used bitwise operators I was able to make mini-versions of this game that only took 5 and 7 lines, small enough for a signature.
Logged

Cymon's Games, free source code, tutorials, and a new game every week!
Follow me on twitter
nihilocrat
Level 10
*****


Full of stars.


View Profile WWW
« Reply #14 on: August 05, 2009, 12:22:36 PM »

Fun hack:

Code:
a^=b^=a^=b^=a;

The values of 'a' and 'b' are now swapped, without the use of an intermediate 'temp' variable.

However, Wikipedia states:
Quote
Most modern compilers can optimize away the temporary variable in the naive swap, in which case the naive swap uses the same amount of memory and the same number of registers as the XOR swap and is at least as fast, and often faster.[2] As a general rule, you should never use the XOR swap unless you know for a fact that the naive swap will not suit your application (which is very rare in this day and age). The XOR swap is also much less readable, and can be completely opaque to anyone who isn't already familiar with the technique.
Logged

Zaphos
Guest
« Reply #15 on: August 05, 2009, 04:00:01 PM »

Wiki also states
Quote
The body of this function is sometimes seen incorrectly shortened to if (x != y) *x^=*y^=*x^=*y;. This code has undefined behavior, since it modifies the lvalue *x twice without an intervening sequence point.
... which would seem to apply to your snippet as well?
Logged
Pages: [1]
Print
Jump to:  

Theme orange-lt created by panic