Tuesday, September 29, 2009

Краткая памятка по битовым операциям


Setting a bit

Use the bitwise OR operator (|) to set a bit.

 number |= 1 << x;

That will set bit x.

Clearing a bit

Use the bitwise AND operator (&) to clear a bit.

 number &= ~(1 << x);

That will clear bit x. You must invert the bit string with the bitwise NOT operator (~), then AND it.

Toggling a bit

The XOR operator (^) can be used to toggle a bit.

 number ^= 1 << x;

That will toggle bit x.

Checking a bit

You didn't ask for this but I might as well add it.

To check a bit, AND it with the bit you want to check:

 bit = number & (1 << x);

That will put the value of bit x into the variable bit.


Toggling a bit and leaving all other bits unchanged

x = x ^ mask;
(or shorthand x ^= mask;)

Bits that are set to 1 in the mask will be toggled in x.
Bits that are set to 0 in the mask will be unchanged in x.

Toggling means that if the bit is 1, it is set to 0, and if the bit is 0, it is set to 1.

XOR truth table
0 ^ 0 = 0
1 ^ 0 = 1
0 ^ 1 = 1
1 ^ 1 = 0


Setting a bit to zero and leaving all other bits unchanged

x = x & mask;
(or x &= mask;)

Bits that are set to 1 in the mask will be unchanged in x.
Bits that are set to 0 in the mask will be set to zero in x.

AND truth table
0 & 0 = 0
1 & 0 = 0
0 & 1 = 0
1 & 1 = 1

Common Flags Values
Binary
(base2)
Hexadecimal
(base16)
Decimal
(base10)
0000 0000 0x00 0
0000 0001 0x01 1
0000 0010 0x02 2
0000 0100 0x04 4
0000 1000 0x08 8
0001 0000 0x10 16
0010 0000 0x20 32
0100 0000 0x40 64
1000 0000 0x80 128

Example macros:

Imagine there are two flags in the program that are independent of each other. We might implement macros to manipulate them as shown in the code sample below. It would probably be wise to put the macros in a header file.


// the flag definitions
#define CAR_LOCKED 0x01 // 0000 0001
#define CAR_PARKED 0x02 // 0000 0010
#define CAR_RESET 0x00 // 0000 0000

// macros to manipulate the flags
#define RESET_CAR(x) (x = CAR_RESET)

#define SET_LOCKED(x) (x |= CAR_LOCKED)
#define SET_PARKED(x) (x |= CAR_PARKED)

#define UNSET_LOCKED(x) (x &= (~CAR_LOCKED))
#define UNSET_PARKED(x) (x &= (~CAR_PARKED))

#define TOGGLE_LOCKED(x) (x ^= CAR_LOCKED)
#define TOGGLE_PARKED(x) (x ^= CAR_PARKED)

// these evaluate to non-zero if the flag is set
#define IS_LOCKED(x) (x & CAR_LOCKED)
#define IS_PARKED(x) (x & CAR_PARKED)

// a short program that demonstrates how to use the macros
int main(void)
{
unsigned char fMercedes, fCivic;

RESET_CAR(fMercedes);
RESET_CAR(fCivic);

SET_LOCKED(fMercedes);

if( IS_LOCKED(fMercedes) != 0 )
{
UNSET_PARKED(fCivic);
}

TOGGLE_LOCKED(fMercedes);

return 0;
}

Использование std::bitset и boost::dynamic_bitset

#include
#include

int main()
{
std
::bitset<5> x;
x
[1] = 1;
x
[2] = 0;
// Note x[0-4] valid
std
::cout << x << std::endl;
}

The other option is to use bit fields:

struct bits {
unsigned int a:1;
unsigned int b:1;
unsigned int c:1;
};

struct bits mybits;

This defines a 3-bit field (actually, it's three 1-bit felds).
Bit operations now become a bit (haha) simpler:

To set or clear a bit:

mybits
.b = 1;
mybits
.c = 0;

To toggle a bit:

mybits
.a = !mybits.a;
mybits
.b = ~mybits.b;
mybits
.c ^= 1; /* all work */

Checking a bit:

if
(mybits.c)
This only works with bits in fixed positions. Otherwise you have to
resort to the bit-twiddling techniques described in previous posts.


It is sometimes worth using an enum to name the bits:
enum ThingFlags = {
ThingMask = 0x0000,
ThingFlag0
= 1 << 0,
ThingFlag1
= 1 << 1,
ThingError = 1 << 8,
}

Then use the names later on. I.e. write

 thingstate |= ThingFlag1;
thingstate
&= ~ThingFlag0;
if (thing | ThingError) {...}

to set, clear and test. This way you hide the magic numbers from the rest of your code.

From snip-c.zip's bitops.how:

/*
** Bit set, clear, and test operations
**
** public domain snippet by Bob Stout
*/
typedef enum {ERROR = -1, FALSE, TRUE} LOGICAL;

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

OK, let's analyze things...

The common expression in all of these that you seem to be having problems with is "(1L << (posn))". All this does is create a mask with a single bit on and which will work with any integer type. The "posn" argument specifies the position where you want the bit. If posn==0, then this expression will evaluate to:

0000 0000 0000 0000 0000 0000 0000 0001 binary.

If posn==8, it will evaluate to

0000 0000 0000 0000 0000 0001 0000 0000 binary.

In other words, it simply creates a field of 0's with a 1 at the specified position. The only tricky part is in the BitClr() macro where we need to set a single 0 bit in a field of 1's. This is accomplished by using the 1's complement of the same expression as denoted by the tilde (~) operator.

Once the mask is created it's applied to the argument just as you suggest, by use of the bitwise and (&), or (|), and xor (^) operators. Since the mask is of type long, the macros will work just as well on char's, short's, int's, or long's.

The bottom line is that this is a general solution to an entire class of problems. It is, of course, possible and even appropriate to rewrite the equivalent of any of these macros with explicit mask values every time you need one, but why do it? Remember, the macro substitution occurs in the preprocessor and so the generated code will reflect the fact that the values are considered constant by the compiler - i.e. it's just as efficient to use the generalized macros as to "reinvent the wheel" every time you need to do bit manipulation.

Unconvinced? Here's some test code - I used Watcom C with full optimization and without using _cdecl so the resulting disassembly would be as clean as possible:

----[ TEST.C ]----------------------------------------------------------------

#define BOOL(x) (!(!(x)))

#define BitSet(arg,posn) ((arg) | (1L << (posn)))
#define BitClr(arg,posn) ((arg) & ~(1L << (posn)))
#define BitTst(arg,posn) BOOL((arg) & (1L << (posn)))
#define BitFlp(arg,posn) ((arg) ^ (1L << (posn)))

int bitmanip(int word)
{
word
= BitSet(word, 2);
word
= BitSet(word, 7);
word
= BitClr(word, 3);
word
= BitFlp(word, 9);
return word;
}

----[ TEST.OUT (disassembled) ]-----------------------------------------------

Module: C:\BINK\tst.c Group: 'DGROUP' CONST,CONST2,_DATA,_BSS

Segment: TEXT BYTE 00000008 bytes
0000 0c 84 bitmanip
or al,84H 0002 80 f4 02 xor ah,02H 0005 24 f7 and al,0f7H 0007 c3 ret

No disassembly errors

еще:
/* a=target variable, b=bit number to act upon 0-n */
#define BIT_SET(a,b) ((a) |= (1<<(b)))
#define BIT_CLEAR(a,b) ((a) &= ~(1<<(b)))
#define BIT_FLIP(a,b) ((a) ^= (1<<(b)))
#define BIT_CHECK(a,b) ((a) & (1<<(b)))

/* x=target variable, y=mask */
#define BITMASK_SET(x,y) ((x) |= (y))
#define BITMASK_CLEAR(x,y) ((x) &= (~(y)))
#define BITMASK_FLIP(x,y) ((x) ^= (y))
#define BITMASK_CHECK(x,y) ((x) & (y))

еще:

The bitfield approach has other advantages in the embedded arena. You can define a struct that maps directly onto the bits in a particular hardware register.
struct HwRegister {
unsigned int errorFlag:1; // one-bit flag field
unsigned int Mode:3; // three-bit mode field
unsigned int StatusCode:4; // four-bit status code
};

struct HwRegister CR3342_AReg;

You need to be aware of the bit packing order - I think it's MSB first, but this may be implementation-dependent. Also, verify how your compiler handlers fields crossing byte boundaries.

You can then read, write, test the individual values as before.

const unsigned char TQuickByteMask[8] =
{
0x01, 0x02, 0x04, 0x08,
0x10, 0x20, 0x40, 0x80,
};

еще:

If you're doing a lot of bit twiddling you might want to use masks
which will make the whole thing quicker. The following functions are
very fast and still flexible (they allow bit twiddling in bit maps of
any size)

/** Set bit in any sized bit mask.
*
* @return none
*
* @param bit - Bit number.
* @param bitmap - Pointer to bitmap.
*/

void TSetBit( short bit, unsigned char *bitmap)
{
short n, x;

x
= bit / 8; // Index to byte.
n
= bit % 8; // Specific bit in byte.

bitmap
[x] |= TQuickByteMask[n]; // Set bit.
}

/** Reset bit in any sized mask.
*
* @return None
*
* @param bit - Bit number.
* @param bitmap - Pointer to bitmap.
*/

void TResetBit( short bit, unsigned char *bitmap)
{
short n, x;

x
= bit / 8; // Index to byte.
n
= bit % 8; // Specific bit in byte.

bitmap
[x] &= (~TQuickByteMask[n]); // Reset bit.
}

/** Toggle bit in any sized bit mask.
*
* @return none
*
* @param bit - Bit number.
* @param bitmap - Pointer to bitmap.
*/

void TToggleBit( short bit, unsigned char *bitmap)
{
short n, x;

x
= bit / 8; // Index to byte.
n
= bit % 8; // Specific bit in byte.

bitmap
[x] ^= TQuickByteMask[n]; // Toggle bit.
}

/** Checks specified bit.
*
* @return 1 if bit set else 0.
*
* @param bit - Bit number.
* @param bitmap - Pointer to bitmap.
*/

short TIsBitSet( short bit, const unsigned char *bitmap)
{
short n, x;

x
= bit / 8; // Index to byte.
n
= bit % 8; // Specific bit in byte.

// Test bit (logigal AND).
if (bitmap[x] & TQuickByteMask[n])
return 1;

return 0;
}

/** Checks specified bit.
*
* @return 1 if bit reset else 0.
*
* @param bit - Bit number.
* @param bitmap - Pointer to bitmap.
*/

short TIsBitReset( short bit, const unsigned char *bitmap)
{
return TIsBitSet( bit, bitmap) ^ 1;
}

/** Count number of bits set in a bitmap.
*
* @return Number of bits set.
*
* @param bitmap - Pointer to bitmap.
* @param size - Bitmap size (in bits).
*
* @note Not very efficient in terms of execution speed. If you are doing
* some computationally intense stuff you may need a more complex
* implementation which would be faster (especially for big bitmaps).
* See (http://graphics.stanford.edu/~seander/bithacks.html).
*/

int TCountBits( const unsigned char *bitmap, int size)
{
int i, count=0;

for (i=0; i<size; i++)
if (TIsBitSet( i, bitmap)) count++;

return count;
}

Note, to set bit 'n' in a 16 bit integer you do the following:

TSetBit( n, &my_int);

It's up to you to ensure that the bit number is within the range of the bit map that you pass. Note that for little endian processors that bytes, words, dwords,qwords etc, map correctly to each other in memory (main reason that little endian processors are 'better' than big-endian processors, ah, I feel a flame war coming on...).

Дополнительные ссылки:
bits.stephan-brumme.com
aggregate.org/MAGIC
realtimecollisiondetection.net/blog/?p=78
graphics.stanford.edu/~seander/bithacks.html
www.somacon.com/p125.php
stackoverflow.com/questions/47981/how-do-you-set-clear-and-toggle-a-single-bit-in-c

0 коммент.:

Post a Comment

Powered by Blogger.