Skip to main content

Lecture 6: Bit Processing

Prerequisites: Functions; integer data types and arithmetic operators.

1. Overview

  • Computers store and process all data as sequences of binary digits (bits); understanding how to read, manipulate, and reason about individual bits is fundamental to systems programming, embedded development, and performance-critical code.
  • After this chapter you will be able to convert between numeral systems, apply the six C bitwise operators (&, |, ^, ~, <<, >>), construct bit masks to set, clear, toggle, and test individual bits, and recognise common bit-level idioms used in hardware programming.

2. Learning Outcomes

After completing this chapter, students will be able to:

  1. Explain the positional-value principle and convert numbers fluently between binary, octal, decimal, and hexadecimal representations.
  2. Describe how non-negative and negative integers are stored in memory using two's complement representation, and perform two's complement conversion by hand.
  3. Distinguish between the six C bitwise operators and predict their results on given operands.
  4. State the precedence and associativity rules for bitwise operators and use parentheses to avoid precedence-related bugs.
  5. Use left shift and right shift for efficient multiplication and division by powers of two, and explain the difference between logical and arithmetic right shifts.
  6. Construct bit masks to set, clear, toggle, and test individual bits or ranges of bits within an integer.
  7. Apply bit-manipulation idioms to practical scenarios such as hardware port control, packet validation, and power-of-two detection.

3. Key Terms

Term (EN)中文Notes
Bit比特(位)The smallest unit of data: 0 or 1.
Byte字节8 bits.
Most Significant Bit (MSB)最高有效位The leftmost bit; in a signed integer it is the sign bit.
Least Significant Bit (LSB)最低有效位The rightmost bit; determines whether a number is odd or even.
Numeral system (radix/base)进制(基数)A writing system for numbers; common bases are 2, 8, 10, 16.
Binary (Base-2)二进制Digits 0–1. C prefix: 0b (GCC/Clang extension) or no standard prefix.
Octal (Base-8)八进制Digits 0–7. C prefix: 0 (leading zero).
Hexadecimal (Base-16)十六进制Digits 0–9, A–F. C prefix: 0x or 0X.
Two's complement补码The standard signed-integer representation: invert all bits and add 1.
Bitwise AND (&)按位与Result bit is 1 only when both operand bits are 1.
Bitwise OR (|)按位或Result bit is 1 when at least one operand bit is 1.
Bitwise XOR (^)按位异或Result bit is 1 when the operand bits differ. Easily confused with exponentiation (which C does not have).
Bitwise NOT (~)按位取反Flips every bit; unary operator.
Left shift (<<)左移Shifts bits leftward, filling with 0 on the right. Equivalent to multiplication by 2^n.
Right shift (>>)右移Shifts bits rightward. For unsigned types, fills with 0 (logical shift); for signed types, typically fills with the sign bit (arithmetic shift — implementation-defined).
Bit mask位掩码A value used with a bitwise operator to select, set, clear, or toggle specific bits.
Set a bit置位Force a bit to 1 using OR with a mask.
Clear a bit清位Force a bit to 0 using AND with the complement of a mask.
Toggle a bit翻转位Flip a bit using XOR with a mask.

4. Core Concepts

4.1 Numeral Systems

A numeral system is a positional notation in which each digit's contribution to the overall value is determined by its position and the base (also called radix) of the system. The general formula for a number with digits dndn1d1d0d_n d_{n-1} \ldots d_1 d_0 in base bb is:

value=dn×bn+dn1×bn1++d1×b1+d0×b0\text{value} = d_n \times b^n + d_{n-1} \times b^{n-1} + \cdots + d_1 \times b^1 + d_0 \times b^0

Four bases are of particular importance in computing:

BaseNameDigit setTypical use
2Binary0, 1Direct hardware representation
8Octal0–7Legacy Unix file permissions; compact binary groups of 3 bits
10Decimal0–9Everyday human arithmetic
16Hexadecimal0–9, A–FCompact binary groups of 4 bits; memory addresses, colour codes

Worked conversions:

  • 10112=1×23+0×22+1×21+1×20=8+0+2+1=11101011_2 = 1\times2^3 + 0\times2^2 + 1\times2^1 + 1\times2^0 = 8+0+2+1 = 11_{10}
  • 110101102=128+64+0+16+0+4+2+0=2141011010110_2 = 128+64+0+16+0+4+2+0 = 214_{10}
  • 7258=7×64+2×8+5×1=448+16+5=46910725_8 = 7\times64 + 2\times8 + 5\times1 = 448+16+5 = 469_{10}
  • 1AF16=1×256+10×16+15×1=256+160+15=43110\text{1AF}_{16} = 1\times256 + 10\times16 + 15\times1 = 256+160+15 = 431_{10}

A handy shortcut: each hexadecimal digit maps to exactly four binary digits (a nibble), and each octal digit maps to exactly three binary digits. This is why hexadecimal is the preferred compact notation for binary data.

Minimal example File: p06_a.c Build: gcc p06_a.c -o p06_a Run: ./p06_a

/* p06_a.c – Printing an integer in decimal, octal, and hexadecimal */
#include <stdio.h>

int main(void) {
int dec = 26; /* decimal literal */
int oct = 032; /* octal literal (= 26 decimal) */
int hex = 0x1A; /* hex literal (= 26 decimal) */

printf("DEC: %d\n", dec);
printf("OCT: %o (DEC: %d)\n", oct, oct);
printf("HEX: %x (DEC: %d)\n", hex, hex);

return 0;
}

What you should see: All three variables hold the same value (26); the format specifiers %d, %o, and %x simply display it in different bases.

Takeaway: The prefix 0 denotes an octal literal in C and 0x denotes a hexadecimal literal. A common trap is writing int x = 010; and expecting it to equal ten—it actually equals eight.

4.2 How Data Are Stored in Memory

Regardless of the numeral system used in source code, all values are ultimately stored as sequences of binary bits. A typical int on modern systems occupies 4 bytes (32 bits). The leftmost bit is called the Most Significant Bit (MSB) and the rightmost is the Least Significant Bit (LSB).

For example, the integer 13 is stored as:

00000000 00000000 00000000 00001101
^ ^
MSB LSB

The bit positions, counted from the right starting at 0, carry the values 20,21,22,,2312^0, 2^1, 2^2, \ldots, 2^{31}. For 13 we have bits 0, 2, and 3 set: 1+4+8=131 + 4 + 8 = 13.

You can verify the size of any type on your system with the sizeof operator:

printf("int is %zu bytes (%zu bits)\n", sizeof(int), sizeof(int) * 8);

Takeaway: Every integer you write in C is, at the hardware level, a fixed-width pattern of bits. Understanding this is the prerequisite for all bitwise operations.

4.3 Two's Complement Representation

Modern computers represent signed integers using two's complement. In this scheme the MSB serves as the sign bit: 0 for non-negative, 1 for negative. Positive numbers are stored in their ordinary binary form. To obtain the representation of a negative number:

  1. Write the binary representation of its positive counterpart.
  2. Invert (flip) every bit.
  3. Add 1.

Example: Representing −13 in an 8-bit signed char:

StepActionBinary
1Start with +1300001101
2Invert all bits11110010
3Add 111110011

Verification: 11110011211110011_2 interpreted as unsigned is 128+64+32+16+0+0+2+1=243128+64+32+16+0+0+2+1 = 243; as a signed byte it is 243256=13243 - 256 = -13.

Two's complement has an elegant property: addition and subtraction use exactly the same circuitry for both signed and unsigned integers, which is why it is universally adopted.

Minimal example File: p06_b.c Build: gcc p06_b.c -o p06_b Run: ./p06_b

/* p06_b.c – Observing two's complement with printf */
#include <stdio.h>

int main(void) {
signed char pos = 13;
signed char neg = -13;

/* Print the raw byte in hexadecimal */
printf("+13 as hex byte: %02X\n", (unsigned char)pos);
printf("-13 as hex byte: %02X\n", (unsigned char)neg);

return 0;
}

What you should see:

+13 as hex byte: 0D
-13 as hex byte: F3

0x0D is 00001101 in binary; 0xF3 is 11110011—exactly the two's complement we computed above.

Takeaway: Two's complement allows the hardware to use a single addition circuit for both signed and unsigned arithmetic. The ~ (bitwise NOT) operator in C performs the "invert" step; adding 1 completes the negation.

4.4 Bitwise Operators: AND, OR, XOR, NOT

C provides four bitwise logical operators that act on each pair of corresponding bits independently:

OperatorSymbolRuleTypical use
AND&1 only if both bits are 1Clear bits; test bits
OR|1 if at least one bit is 1Set bits
XOR^1 if bits differToggle bits; swap without temp
NOT~Inverts every bit (unary)Build complement masks

The truth tables are worth memorising:

AND OR XOR NOT
A B A&B A B A|B A B A^B A ~A
0 0 0 0 0 0 0 0 0 0 1
0 1 0 0 1 1 0 1 1 1 0
1 0 0 1 0 1 1 0 1
1 1 1 1 1 1 1 1 0

A useful way to remember the masking properties:

  • x & 0 = 0 (clears), x & 1 = x (preserves)
  • x | 0 = x (preserves), x | 1 = 1 (sets)
  • x ^ 0 = x (preserves), x ^ 1 = ~x (toggles)

Minimal example File: p06_c.c Build: gcc p06_c.c -o p06_c Run: ./p06_c

/* p06_c.c – All four bitwise logical operators */
#include <stdio.h>

int main(void) {
int a = 3, b = 5;
/* a = ...0011
b = ...0101 */

printf("a & b = %d\n", a & b); /* ...0001 → 1 */
printf("a | b = %d\n", a | b); /* ...0111 → 7 */
printf("a ^ b = %d\n", a ^ b); /* ...0110 → 6 */
printf("~a = %d\n", ~a); /* all bits flipped → -4 (two's complement) */

return 0;
}

What you should see:

a & b = 1
a | b = 7
a ^ b = 6
~a = -4

Takeaway: Bitwise operators work on the full width of the operand type (typically 32 bits for int). The result of ~a on a signed integer is negative because the MSB becomes 1.

4.5 Bit Shifting: Left Shift and Right Shift

The shift operators move the entire bit pattern of a value left or right by a specified number of positions.

Left shift (<<): vacated positions on the right are filled with 0. Each shift by 1 is equivalent to multiplying by 2.

valuen  =  value×2n\text{value} \ll n \;=\; \text{value} \times 2^n

Right shift (>>): for unsigned types, vacated positions on the left are filled with 0 (logical shift). For signed types, the behaviour is implementation-defined, but most compilers perform an arithmetic shift, filling with copies of the sign bit.

valuen  =  value  /  2n(for non-negative values)\text{value} \gg n \;=\; \lfloor \text{value} \;/\; 2^n \rfloor \quad \text{(for non-negative values)}

Minimal example File: p06_d.c Build: gcc p06_d.c -o p06_d Run: ./p06_d

/* p06_d.c – Left shift and right shift */
#include <stdio.h>

int main(void) {
unsigned char num = 22; /* 00010110 */

unsigned char left = num << 2; /* 01011000 = 88 */
unsigned char right = num >> 1; /* 00001011 = 11 */

printf("Original: %3d\n", num);
printf("Left << 2: %3d (×4)\n", left);
printf("Right >> 1: %3d (÷2)\n", right);

return 0;
}

What you should see:

Original: 22
Left << 2: 88 (×4)
Right >> 1: 11 (÷2)

Signed right shift example: A signed char holding −16 (11110000 in two's complement) shifted right by 2 yields −4 (11111100). The sign bit is preserved because the compiler performs an arithmetic shift:

-16: 1 1 1 1 0 0 0 0
>> 2: 1 1 1 1 1 1 0 0 → -4
^
sign bit replicated

Takeaway: Shift operations are among the fastest instructions on any processor. Use them in preference to multiplication or division by powers of two when performance matters, but be cautious with signed right shifts and always use unsigned types for portable, predictable bit manipulation.

4.6 Operator Precedence for Bitwise Operators

The precedence of the bitwise operators relative to one another and to the comparison operators is a frequent source of bugs. The following table lists them from highest to lowest precedence:

PrecedenceOperatorAssociativity
Highest~ (unary NOT)Right-to-left
<<, >>Left-to-right
&Left-to-right
^Left-to-right
Lowest|Left-to-right

Critically, the comparison operators (==, !=, <, >, etc.) have higher precedence than &, ^, and |. This means:

if (x & 0x0F == 0) /* WRONG: parsed as x & (0x0F == 0) → x & 0 */
if ((x & 0x0F) == 0) /* CORRECT */

Takeaway: When in doubt, add parentheses around every bitwise sub-expression that participates in a comparison. The cost of extra parentheses is zero at run time, but the cost of a precedence bug can be hours of debugging.

4.7 Bit Masking: Set, Clear, Toggle, and Test

A bit mask is a value whose bit pattern selects which bits of another value to modify or inspect. The four fundamental masking operations are:

Set a bit (force to 1) — use OR:

number |= (1 << bit_position);

Clear a bit (force to 0) — use AND with the complement of the mask:

number &= ~(1 << bit_position);

Toggle a bit (flip) — use XOR:

number ^= (1 << bit_position);

Test a bit (check whether it is 1) — use AND:

if (number & (1 << bit_position)) /* bit is set */

These four idioms cover the vast majority of bit-manipulation tasks.

Extended example — clearing a range of bits: To clear bits m through n (inclusive, 0-indexed), construct a mask with 1s in those positions, then AND with its complement:

/* Build a mask with 1s from bit m to bit n */
unsigned int mask = ((1U << (n - m + 1)) - 1) << m;
number &= ~mask;

For instance, clearing bits 1 through 3 of 0xFF (11111111):

mask = ((1 << 3) - 1) << 1 = 0b00001110
~mask = 0b11110001
0xFF & ~mask = 0b11110001 = 241

Minimal example File: p06_e.c Build: gcc p06_e.c -o p06_e Run: ./p06_e

/* p06_e.c – Set, clear, toggle, and test bits */
#include <stdio.h>

int main(void) {
unsigned char number = 0x2D; /* 00101101 */

/* Clear bit 2 */
number &= ~(1 << 2);
printf("After clearing bit 2: 0x%02X\n", number); /* 0x29 */

/* Set bit 4 */
number |= (1 << 4);
printf("After setting bit 4: 0x%02X\n", number); /* 0x39 */

/* Toggle bits 0 and 1 */
number ^= (1 << 0) | (1 << 1);
printf("After toggling bits 0,1: 0x%02X\n", number); /* 0x3A */

/* Test bit 5 */
if (number & (1 << 5))
printf("Bit 5 is set.\n");
else
printf("Bit 5 is not set.\n");

return 0;
}

What you should see:

After clearing bit 2: 0x29
After setting bit 4: 0x39
After toggling bits 0,1: 0x3A
Bit 5 is set.

Takeaway: The expression (1 << k) creates a mask with only bit k set; combining multiple such expressions with | builds a multi-bit mask. Master these four idioms and you can handle virtually any bit-manipulation requirement.

4.8 Practical Bit Manipulation: Embedded-Systems Patterns

In embedded programming, hardware peripherals are controlled through memory-mapped registers, where each bit (or group of bits) controls a specific feature—an LED, a motor, a sensor enable line, and so forth. The patterns described in §4.7 appear constantly in this context, often wrapped in a polling loop.

Testing if a bit is set:

if (PORT & (1 << 3)) { /* bit 3 is high */ }

Testing if a bit is cleared:

if (!(PORT & (1 << 4))) { /* bit 4 is low */ }

Waiting (polling) until a bit becomes set:

while (!(PORT & (1 << 0))) {
/* busy-wait; in real hardware PORT is volatile */
}

Waiting until a bit becomes cleared:

while (PORT & (1 << 7)) {
/* busy-wait */
}

Checking multiple bits at once: To verify that all bits specified by a mask are set:

#define MASK 0x15 /* bits 0, 2, 4 */
if ((PORT & MASK) == MASK) { /* all three bits are set */ }

Note the use of == rather than just truthy evaluation: (PORT & MASK) is non-zero if any of the masked bits are set, whereas (PORT & MASK) == MASK is true only if all of them are.

Takeaway: In real embedded code the register variable would be declared volatile to prevent the compiler from optimising away repeated reads. The logical patterns, however, are identical to those presented here.

5. Worked Examples

Example 1: Counting Set Bits (Population Count)

Problem: Given a non-negative integer n, count how many bits in its binary representation are set to 1. For example, 2310=10111223_{10} = 10111_2 has four set bits.

Steps:

  1. Initialise a counter to 0.
  2. While n is not zero, check its LSB with n & 1.
  3. If the LSB is 1, increment the counter.
  4. Right-shift n by 1 to examine the next bit.
  5. When n reaches 0, all bits have been inspected.

Reference code File: p06_ex1.c Build: gcc p06_ex1.c -o p06_ex1 Run: ./p06_ex1

/* p06_ex1.c – Count set bits (popcount) */
#include <stdio.h>

int count_set_bits(unsigned int n);

int main(void) {
unsigned int n;
printf("Enter a non-negative integer: ");
scanf("%u", &n);
printf("%d\n", count_set_bits(n));
return 0;
}

int count_set_bits(unsigned int n) {
int count = 0;
while (n) {
count += (n & 1);
n >>= 1;
}
return count;
}

Output:

Enter a non-negative integer: 23
4

Why it works: The expression n & 1 isolates the least significant bit. Right-shifting by 1 effectively moves the next bit into the LSB position. The loop terminates when all bits have been shifted out (i.e., n == 0).

Example 2: Detecting a Power of Two

Problem: Determine whether a given positive integer is a power of two.

Steps:

  1. Observe that a power of two in binary has exactly one bit set: e.g., 8=100028 = 1000_2, 16=10000216 = 10000_2.
  2. The value one less than a power of two has all lower bits set: 7=011127 = 0111_2, 15=01111215 = 01111_2.
  3. Therefore n & (n - 1) is zero if and only if n is a power of two (assuming n > 0).

Reference code File: p06_ex2.c Build: gcc p06_ex2.c -o p06_ex2 Run: ./p06_ex2

/* p06_ex2.c – Is the number a power of two? */
#include <stdio.h>

int is_power_of_two(unsigned int n);

int main(void) {
unsigned int n;
printf("Enter a positive integer: ");
scanf("%u", &n);

if (is_power_of_two(n))
printf("Yes\n");
else
printf("No\n");

return 0;
}

int is_power_of_two(unsigned int n) {
return (n > 0) && ((n & (n - 1)) == 0);
}

Output:

Enter a positive integer: 64
Yes

Why it works: If n has exactly one bit set, then n - 1 flips that bit to 0 and sets all lower bits to 1. ANDing the two produces zero. If n has more than one bit set, at least one bit remains after the AND.

Example 3: Synchronise Bit Pattern with a Mask

Problem: Given two integers a and b and a bitmask m, copy the bits of b into a at the positions where m has 1s, leaving all other bits of a unchanged.

Steps:

  1. Clear the masked positions in a: a &= ~m;
  2. Extract the relevant bits from b: b & m
  3. Combine: a |= (b & m);

Reference code File: p06_ex3.c Build: gcc p06_ex3.c -o p06_ex3 Run: ./p06_ex3

/* p06_ex3.c – Synchronise selected bits from b into a */
#include <stdio.h>

unsigned int sync_bits(unsigned int a, unsigned int b, unsigned int m);

int main(void) {
unsigned int a, b, m;
scanf("%u %u %u", &a, &b, &m);
printf("%u\n", sync_bits(a, b, m));
return 0;
}

unsigned int sync_bits(unsigned int a, unsigned int b, unsigned int m) {
a &= ~m; /* clear masked positions in a */
a |= (b & m); /* copy those positions from b */
return a;
}

Output (for input 100 185 12):

104

Why it works: Consider the bit-level trace:

a = 0110 0100 (100)
b = 1011 1001 (185)
m = 0000 1100 (12)
~m = 1111 0011

a & ~m = 0110 0000 (clear bits 2–3 of a)
b & m = 0000 1000 (extract bits 2–3 of b)
result = OR = 0110 1000 (104)

The formula (a & ~m) | (b & m) is a standard idiom known as bit-field insertion or masked merge.

6. Common Mistakes

  1. Confusing & with && (and | with ||): The single-character operators are bitwise; the double-character operators are logical (short-circuit) Boolean operators. Writing if (x & y) when you mean if (x && y) (or vice versa) produces silently wrong results.

  2. Forgetting parentheses around bitwise expressions in comparisons: Because == binds more tightly than &, ^, and |, the expression x & 0x0F == 0 is parsed as x & (0x0F == 0), which evaluates to x & 0 — always 0. Always write (x & 0x0F) == 0.

  3. Using signed types for bit manipulation: Right-shifting a negative signed integer is implementation-defined. Prefer unsigned int or unsigned char for all bit-manipulation work to guarantee predictable, portable results.

  4. Shifting by a negative amount or by ≥ width of the type: Both are undefined behaviour in C. For a 32-bit unsigned int, valid shift counts are 0 through 31.

  5. Accidentally writing an octal literal: A leading 0 in an integer literal makes it octal. Writing int mask = 010; intending decimal 10 actually gives 8. Use 0x prefix for hexadecimal masks or write the decimal value explicitly.

  6. Assuming ~0 is 0xFF: The ~ operator flips all bits of the operand type. If the operand is int, ~0 produces a 32-bit value 0xFFFFFFFF, not an 8-bit 0xFF. Use an explicit cast or an appropriate-width type when the width matters.

  7. Overflow with left shift: Shifting a 1 into or past the sign bit of a signed integer is undefined behaviour. Use 1U << k (unsigned literal) instead of 1 << k when building masks for high bit positions.

7. Practice

7.1 Basic

  • P1: Write a programme that reads a non-negative integer and prints its binary representation (as a string of 0s and 1s). Use a loop with right-shift and AND to extract each bit from MSB to LSB. Input/Output: Enter a number: 13Binary: 00000000000000000000000000001101 Acceptance: Correct for 0, 1, and large values; always prints 32 digits.

  • P2: Write a programme that reads two integers and prints the result of each bitwise operator (&, |, ^, ~ on the first, << by 2, >> by 2). Input/Output: Enter a and b: 3 5 → six lines of output showing each result. Acceptance: Output matches manual calculation.

7.2 Required

  • P3: (Count Set Bits) Given a non-negative integer n (0n1060 \le n \le 10^6), count how many bits are set to 1 in its binary representation. Input/Output: 234 Acceptance: Correct for edge cases 0 (output 0) and 1 (output 1).

  • P4: (Toggle a Specific Bit) Given an integer n and a bit position k (0-indexed from the right, 0k310 \le k \le 31), toggle the bit at position k and print the result. Input/Output: 21 217 Acceptance: Toggling the same bit twice returns the original value.

  • P5: (Is Power of Two) Check whether a given positive integer is a power of two. Print Yes or No. Input/Output: 64Yes; 5No Hint: A power of two has exactly one set bit; n & (n - 1) == 0. Acceptance: Handles 1 correctly (1 is 202^0, so output Yes).

  • P6: (Clear Bit Range) Given a non-negative integer and two positions m and n (0mn310 \le m \le n \le 31), clear all bits from position m to n (inclusive). Print the result. Input/Output: 255 1 3241 Acceptance: Works when m == n (single bit) and when the entire range is already zero.

  • P7: (Bitwise Packet Validation) Given n packet values (each 0–255), count how many have bit 7 (the MSB of a byte) set to 1. Input: First line: 5; second line: 128 64 255 1 200 Output: 3 Acceptance: An input of 0 packets produces output 0.

7.3 Optional

  • P8: (Synchronise Bit Pattern) Given integers a, b, and bitmask m, set the bits in a to match those in b wherever m has a 1. Print the modified a. Input/Output: 100 185 12104 Hint: Use the formula (a & ~m) | (b & m). Acceptance: If m is 0, the output equals the original a; if m is all 1s, the output equals b.

  • P9: (Swap Two Integers Without a Temporary Variable) Use three consecutive XOR operations to swap two integers without any auxiliary variable. Print the values before and after the swap. Hint: a ^= b; b ^= a; a ^= b; Acceptance: Works correctly, including when both integers are equal.

  • P10: (Binary to Decimal Converter) Write a programme that reads a string of up to 32 characters consisting of '0' and '1' and prints its decimal value. Use left-shift and OR in a loop rather than pow(). Input/Output: 1011123 Acceptance: Handles leading zeros and the single-character inputs "0" and "1".

8. Mini Task

Goal: Build a bit-field status register simulator. The programme models an 8-bit status register (like those found on microcontrollers) and provides an interactive menu that lets the user set, clear, toggle, and test individual bits, display the register in binary and hexadecimal, and quit.

Deliverables:

  • README.md: Explains how to compile and run the programme, and includes a sample session.
  • src/bitreg.c: The main source file. Structure the code so that each operation (set, clear, toggle, test, display) is its own function.
  • report.md: A short reflection (150–300 words) on what you learnt about bit manipulation from building this tool.

9. Further Reading

  1. Kernighan, B. W. & Ritchie, D. M. (1988). The C Programming Language, 2nd edn. Prentice Hall — §2.9 Bitwise Operators.
  2. Warren, H. S. (2012). Hacker's Delight, 2nd edn. Addison-Wesley — a comprehensive catalogue of bit-manipulation algorithms and tricks.
  3. GNU C Library (glibc) <stdint.h> documentation — fixed-width integer types (uint8_t, uint32_t, etc.) for portable bit manipulation.

10. Appendix

A. Numeral-System Reference Table (0–15)

DecimalBinaryOctalHexadecimal
0000000
1000111
2001022
3001133
4010044
5010155
6011066
7011177
81000108
91001119
10101012A
11101113B
12110014C
13110115D
14111016E
15111117F

B. Bitwise Operator Quick Reference

OperationC expressionEffect
Set bit kx |= (1U << k);Forces bit k to 1
Clear bit kx &= ~(1U << k);Forces bit k to 0
Toggle bit kx ^= (1U << k);Flips bit k
Test bit kif (x & (1U << k))Non-zero if bit k is 1
Clear bits mnx &= ~(((1U << (n-m+1)) - 1) << m);Zeros a contiguous range
Multiply by 2n2^nx << nLeft shift
Divide by 2n2^nx >> n (unsigned)Logical right shift

C. Printf Format Specifiers for Integer Bases

SpecifierBaseExample output for 26
%dDecimal26
%oOctal32
%xHexadecimal (lower-case)1a
%XHexadecimal (upper-case)1A
%uUnsigned decimal26

D. Two's Complement Range for Common Types

TypeWidthSigned rangeUnsigned range
char8 bits−128 to 1270 to 255
short16 bits−32 768 to 32 7670 to 65 535
int32 bits−2 147 483 648 to 2 147 483 6470 to 4 294 967 295
long long64 bits−9.2 × 10¹⁸ to 9.2 × 10¹⁸0 to 1.8 × 10¹⁹