Skip to main content

Python Binary, Bits, and Bytes Practice Problems & Exercises

Practice: Binary, Bits, and Bytes

12 problems4 Easy5 Medium3 Hard45–60 min
← Back to lesson

Easy

#1Decimal to Binary StringEasy
binaryconversiondivision-algorithm

Write a function decimal_to_binary(n) that converts a non-negative integer to its binary string representation without using bin(), format(), or f-string formatting. Implement the repeated-division algorithm from scratch.

print(f"0 -> {decimal_to_binary(0)}")
print(f"1 -> {decimal_to_binary(1)}")
print(f"10 -> {decimal_to_binary(10)}")
print(f"42 -> {decimal_to_binary(42)}")
print(f"255 -> {decimal_to_binary(255)}")
Solution
def decimal_to_binary(n):
if n == 0:
return "0"
bits = []
while n > 0:
bits.append(str(n % 2))
n //= 2
return "".join(reversed(bits))

How it works: The repeated-division algorithm extracts one bit at a time from the least significant position. n % 2 gives the current rightmost bit (0 or 1), and n //= 2 shifts the number right by one position. The remainders come out in reverse order (LSB first), so we reverse them at the end.

Verification: 42 // 2 = 21 r0, 21 // 2 = 10 r1, 10 // 2 = 5 r0, 5 // 2 = 2 r1, 2 // 2 = 1 r0, 1 // 2 = 0 r1 → reversed: 101010.

def decimal_to_binary(n):
    """Convert a non-negative integer to its binary string representation.
    Do NOT use bin(), format(), or f-strings. Implement the algorithm manually.
    Return '0' for input 0.
    """
    pass
Expected Output
0 -> 0\n1 -> 1\n10 -> 1010\n42 -> 101010\n255 -> 11111111
Hints

Hint 1: Repeatedly divide by 2 and collect the remainders. The binary representation is the remainders read in reverse order.

Hint 2: Use a while loop: remainder = n % 2, n = n // 2. Prepend each remainder to your result string, or collect them in a list and reverse at the end.

#2Count Set BitsEasy
binarybit-countingpopcount

Write a function count_set_bits(n) that returns the number of 1 bits (also called "popcount" or "Hamming weight") in the binary representation of a non-negative integer. Do not use bin(n).count('1').

for val in [0, 1, 7, 42, 255, 1023]:
print(f"{val} -> {count_set_bits(val)}")
Solution
def count_set_bits(n):
count = 0
while n:
count += n & 1
n >>= 1
return count

Alternative — Brian Kernighan's algorithm (faster):

def count_set_bits(n):
count = 0
while n:
n &= n - 1 # Clears the lowest set bit
count += 1
return count

Why Kernighan's is faster: The basic approach loops once per bit (up to 32 or 64 times). Kernighan's only loops once per set bit. For n = 1024 (binary 10000000000), the basic approach loops 11 times, but Kernighan's loops just once.

How n & (n-1) works: Subtracting 1 flips the lowest set bit and all bits below it. ANDing with the original clears exactly that lowest set bit. Example: 1010 & 1001 = 1000.

def count_set_bits(n):
    """Return the number of 1-bits in the binary representation of n.
    Do NOT use bin().count('1'). Implement it using bitwise operations.
    """
    pass
Expected Output
0 -> 0\n1 -> 1\n7 -> 3\n42 -> 3\n255 -> 8\n1023 -> 10
Hints

Hint 1: Check the last bit with `n & 1` — it is 1 if the bit is set, 0 otherwise. Then right-shift with `n >>= 1`.

Hint 2: Brian Kernighan trick: `n & (n - 1)` clears the lowest set bit. Count how many times you can do this before n becomes 0.

#3Bitwise Operator Output PredictionEasy
bitwiseANDORXORNOT

Predict the output of each bitwise operation before running. Write out the binary to verify.

Python
a = 0b1010  # 10
b = 0b1111  # 15

print(f"AND: {a & b}")
print(f"OR:  {a | b}")
print(f"XOR: {a ^ b}")
print(f"NOT: {~a}")
print(f"Left shift:  {a << 1}")
print(f"Right shift: {a >> 1}")
Solution
AND: 8
OR: 15
XOR: 7
NOT: -11
Left shift: 20
Right shift: 5

Bit-by-bit breakdown:

a = 1010
b = 1111

a & b = 1010 (both bits must be 1) = 8
a | b = 1111 (either bit can be 1) = 15
a ^ b = 0111 (bits must differ) = 7
~a = ...10101 → -(10 + 1) = -11 (two's complement)
a << 1 = 10100 (shift left = multiply by 2) = 20
a >> 1 = 0101 (shift right = floor divide by 2) = 5

Key insight: ~n = -(n+1) in Python because Python integers have unlimited precision, so the bitwise NOT flips infinitely many leading zeros into ones, giving a negative two's complement result. This catches many beginners off guard.

Expected Output
AND: 8\nOR:  15\nXOR: 7\nNOT: -11\nLeft shift:  20\nRight shift: 5
Hints

Hint 1: Write out the binary representations: 10 = 1010, 15 = 1111, 8 = 1000. Apply each operation bit by bit.

Hint 2: `~n` in Python gives `-(n+1)` because Python uses two's complement with unlimited precision. `~10` = `-11`.

#4UTF-8 Byte CounterEasy
encodingUTF-8bytes

Run the utf8_byte_breakdown function on several strings and predict the total byte count before running.

Python
def utf8_byte_breakdown(text):
    for char in text:
        byte_count = len(char.encode('utf-8'))
        code_point = ord(char)
        print(f"'{char}' U+{code_point:04X} -> {byte_count} byte(s)")
    total = len(text.encode('utf-8'))
    print(f"Total: {len(text)} characters, {total} bytes")

# Test 1: Pure ASCII
utf8_byte_breakdown("Hi!")
print()

# Test 2: Try these and predict before running
# utf8_byte_breakdown("café")
# utf8_byte_breakdown("hello 🌍")
# utf8_byte_breakdown("漢字")

Challenge: Without running the code, predict the byte counts for "café", "hello 🌍", and "漢字".

Solution
"Hi!" -> 3 characters, 3 bytes (all ASCII = 1 byte each)

"café" -> 4 characters, 5 bytes
'c' -> 1, 'a' -> 1, 'f' -> 1, 'é' -> 2 (U+00E9, needs 2 UTF-8 bytes)

"hello 🌍" -> 7 characters, 10 bytes
'h','e','l','l','o',' ' -> 6 bytes (ASCII)
'🌍' -> 4 bytes (U+1F30D, emoji in supplementary plane)

"漢字" -> 2 characters, 6 bytes
'漢' -> 3 bytes (U+6F22, CJK ideograph)
'字' -> 3 bytes (U+5B57, CJK ideograph)

UTF-8 byte ranges:

Unicode rangeUTF-8 bytesExamples
U+0000 - U+007F1 byteASCII: A, z, 0, !
U+0080 - U+07FF2 bytese with accent, Greek, Cyrillic
U+0800 - U+FFFF3 bytesCJK, Japanese, Korean, most symbols
U+10000 - U+10FFFF4 bytesEmoji, rare scripts, math symbols

Key insight: len("café") returns 4 (characters), but len("café".encode("utf-8")) returns 5 (bytes). In networking, file I/O, and databases, it is the byte count that matters for storage and transmission.

def utf8_byte_breakdown(text):
    """Print each character, its Unicode code point, and its UTF-8 byte count."""
    for char in text:
        byte_count = len(char.encode('utf-8'))
        code_point = ord(char)
        print(f"'{char}' U+{code_point:04X} -> {byte_count} byte(s)")
    total = len(text.encode('utf-8'))
    print(f"Total: {len(text)} characters, {total} bytes")
Expected Output
'H' U+0048 -> 1 byte(s)\n'i' U+0069 -> 1 byte(s)\n'!' U+0021 -> 1 byte(s)\nTotal: 3 characters, 3 bytes
Hints

Hint 1: ASCII characters (U+0000 to U+007F) take 1 byte in UTF-8. Many accented characters take 2 bytes. CJK characters and emoji typically take 3-4 bytes.

Hint 2: Use `len(char.encode("utf-8"))` to get the byte count for a single character, or `ord(char)` to get its Unicode code point.


Medium

#5Binary to Decimal ConverterMedium
binaryconversionpositional-notation

Write a function binary_to_decimal(binary_str) that converts a binary string to its decimal integer value without using int(binary_str, 2). Implement the positional notation algorithm from scratch.

for b in ["0", "1", "1010", "101010", "11111111", "10000000000"]:
print(f"{b} -> {binary_to_decimal(b)}")
Solution

Approach 1 — Positional notation (sum of powers):

def binary_to_decimal(binary_str):
result = 0
for i, bit in enumerate(reversed(binary_str)):
if bit == '1':
result += 2 ** i
return result

Approach 2 — Horner's method (more efficient, no exponentiation):

def binary_to_decimal(binary_str):
result = 0
for bit in binary_str:
result = result * 2 + int(bit)
return result

Why Horner's method is better: It processes bits left-to-right in a single pass with no exponentiation. For 101010: 0*2+1=1, 1*2+0=2, 2*2+1=5, 5*2+0=10, 10*2+1=21, 21*2+0=42. Each step doubles the accumulated value (shifting it left by one position) and adds the new bit.

Approach 3 — Using bit shifting (most "binary-native"):

def binary_to_decimal(binary_str):
result = 0
for bit in binary_str:
result = (result << 1) | int(bit)
return result

This is identical to Horner's method but uses << 1 (left shift = multiply by 2) and | int(bit) (OR to set the lowest bit).

def binary_to_decimal(binary_str):
    """Convert a binary string (e.g., '101010') to its decimal integer value.
    Do NOT use int(binary_str, 2). Implement the positional notation algorithm.
    """
    pass
Expected Output
0 -> 0\n1 -> 1\n1010 -> 10\n101010 -> 42\n11111111 -> 255\n10000000000 -> 1024
Hints

Hint 1: Each position i (from right, starting at 0) contributes bit_value * 2^i to the total. Sum them all up.

Hint 2: Alternative approach: Horner's method — start from the left, and for each bit do `result = result * 2 + bit`. This avoids computing powers of 2.

#6RGB Extraction with Bit MaskingMedium
bit-maskingshiftRGBcolor

Write a function hex_to_rgb(hex_color) that extracts the red, green, and blue components from a 24-bit hex color integer using bitwise operations only (no string conversion).

colors = [0xFF8800, 0x00FF00, 0x336699, 0x000000, 0xFFFFFF]
for c in colors:
print(f"0x{c:06X} -> {hex_to_rgb(c)}")
Solution
def hex_to_rgb(hex_color):
r = (hex_color >> 16) & 0xFF
g = (hex_color >> 8) & 0xFF
b = hex_color & 0xFF
return (r, g, b)

How the bit layout works:

Hex color: 0xFF8800
Binary: 11111111 10001000 00000000
^^^^^^^^ ^^^^^^^^ ^^^^^^^^
Red=255 Green=136 Blue=0

Extraction:
Red: >> 16 moves RRRRRRRR to lowest 8 bits, & 0xFF keeps only those 8
Green: >> 8 moves GGGGGGGG to lowest 8 bits, & 0xFF keeps only those 8
Blue: no shift needed, & 0xFF keeps lowest 8 bits

Bonus — the reverse operation (packing RGB into a hex integer):

def rgb_to_hex(r, g, b):
return (r << 16) | (g << 8) | b

Real-world use: This exact technique is used in image processing, game engines, CSS color manipulation, and LED control. The mask 0xFF (255 in decimal, 11111111 in binary) is one of the most common constants in systems programming.

def hex_to_rgb(hex_color):
    """Extract R, G, B values from a hex color integer.
    Example: 0xFF8800 -> (255, 136, 0)
    Use bitwise operations, not string parsing.
    """
    pass
Expected Output
0xFF8800 -> (255, 136, 0)\n0x00FF00 -> (0, 255, 0)\n0x336699 -> (51, 102, 153)\n0x000000 -> (0, 0, 0)\n0xFFFFFF -> (255, 255, 255)
Hints

Hint 1: A 24-bit color is laid out as RRRRRRRRGGGGGGGGBBBBBBBB. Right-shift to move the component you want into the lowest 8 bits, then mask with 0xFF.

Hint 2: Red = (color >> 16) & 0xFF, Green = (color >> 8) & 0xFF, Blue = color & 0xFF. The mask 0xFF = 11111111 in binary isolates the bottom 8 bits.

#7Power of Two CheckerMedium
bitwisepower-of-twobit-trick

Write a function is_power_of_two(n) that returns True if n is a power of 2. Use bitwise operations only — no loops, no math.log, no string conversion.

for val in [0, 1, 2, 3, 4, 16, 17, 1024, -8]:
print(f"{val} -> {is_power_of_two(val)}")
Solution
def is_power_of_two(n):
return n > 0 and (n & (n - 1)) == 0

Why it works:

Powers of 2 have exactly one set bit:
1 = 00000001
2 = 00000010
4 = 00000100
8 = 00001000
16 = 00010000

n - 1 flips the set bit and sets all lower bits:
16 = 00010000
15 = 00001111

n & (n-1) = 00000000 → zero! Only possible for powers of 2.

Non-power: 6 = 00000110, 5 = 00000101, 6 & 5 = 00000100 ≠ 0

Edge cases:

  • n = 0: Not a power of 2 (0 has no set bits). The n > 0 check handles this.
  • n < 0: Negative numbers are never powers of 2. The n > 0 check handles this.
  • n = 1: Is 2^0, which is a valid power of 2. 1 & 0 = 0, so it correctly returns True.

Performance: This is O(1) — a single comparison and a single bitwise operation. This is the standard trick used in operating systems (checking memory alignment), hash table implementations (checking capacity), and graphics engines.

def is_power_of_two(n):
    """Return True if n is a power of 2 (1, 2, 4, 8, 16, ...).
    Use a single bitwise operation — no loops, no log.
    """
    pass
Expected Output
0 -> False\n1 -> True\n2 -> True\n3 -> False\n4 -> True\n16 -> True\n17 -> False\n1024 -> True\n-8 -> False
Hints

Hint 1: A power of 2 in binary has exactly one set bit: 1, 10, 100, 1000, etc. What happens when you subtract 1 from such a number?

Hint 2: If n is a power of 2, then n-1 has all lower bits set (e.g., 1000 - 1 = 0111). So `n & (n-1)` is 0. But you also need to handle n <= 0.

#8Floating Point Precision FixMedium
IEEE-754floating-pointdecimal-module

The expression 0.1 + 0.2 does not equal 0.3 in Python (or any IEEE 754 language). Demonstrate the problem and then fix it using the decimal module.

Python
from decimal import Decimal

# The problem
naive = 0.1 + 0.2
print(f"Naive:   {naive}")
print(f"Fixed:   {0.3}")
print(f"Equal?   {naive == 0.3}")

# The fix — fill in the correct Decimal usage
d1 = Decimal('0.1')
d2 = Decimal('0.2')
d3 = Decimal('0.3')
print(f"Decimal: {d1 + d2 == d3}")
Solution
from decimal import Decimal

# The problem: IEEE 754 binary floating point
naive = 0.1 + 0.2
print(f"Naive: {naive}") # 0.30000000000000004
print(f"Fixed: {0.3}") # 0.3 (display rounds, but value is still imprecise)
print(f"Equal? {naive == 0.3}") # False

# The fix: decimal module uses base-10 arithmetic
d1 = Decimal('0.1') # MUST use strings, not Decimal(0.1)
d2 = Decimal('0.2')
d3 = Decimal('0.3')
print(f"Decimal: {d1 + d2 == d3}") # True

Why this happens: 0.1 in binary is 0.0001100110011... (repeating forever), just like 1/3 in decimal is 0.333.... IEEE 754 truncates this to 53 significant bits, introducing a tiny error of about 5.5 x 10^-17. When you add two such approximations, the error accumulates.

Critical mistake: Decimal(0.1) imports the float error into the Decimal. Always use Decimal('0.1') (string) to get exact base-10 representation.

Other approaches for comparing floats:

import math
math.isclose(0.1 + 0.2, 0.3) # True (default rel_tol=1e-9)
abs(0.1 + 0.2 - 0.3) < 1e-9 # True (manual epsilon)
round(0.1 + 0.2, 10) == round(0.3, 10) # True (round then compare)

When to use decimal: Financial calculations, currency, tax, anywhere exact base-10 arithmetic is required. For scientific computing, math.isclose() is usually sufficient.

Expected Output
Naive:   0.30000000000000004\nFixed:   0.3\nEqual?   False\nDecimal: True
Hints

Hint 1: 0.1 and 0.2 cannot be represented exactly in binary (IEEE 754). They are stored as infinitely repeating fractions truncated to 53 bits of precision.

Hint 2: The `decimal` module uses base-10 arithmetic internally, so 0.1 + 0.2 = 0.3 exactly. Initialize Decimal from strings, not floats, to avoid importing the float error.

#9XOR SwapMedium
XORbitwiseswapbit-trick

Implement xor_swap(a, b) that swaps two integers using only XOR operations — no temporary variable, no tuple unpacking, no arithmetic.

pairs = [(42, 17), (0, 255), (-5, 100)]
for a, b in pairs:
print(f"Before: a={a}, b={b}")
a, b = xor_swap(a, b)
print(f"After: a={a}, b={b}")
Solution
def xor_swap(a, b):
a = a ^ b # a now holds a^b
b = a ^ b # b = (a^b)^b = a (original a)
a = a ^ b # a = (a^b)^a = b (original b)
return a, b

Step-by-step trace with a=42, b=17:

Start: a = 42 (101010), b = 17 (010001)

Step 1: a = a ^ b = 101010 ^ 010001 = 111011 (59)
Step 2: b = a ^ b = 111011 ^ 010001 = 101010 (42) ← original a!
Step 3: a = a ^ b = 111011 ^ 101010 = 010001 (17) ← original b!

Why it works: XOR is self-inverse: x ^ x = 0 and x ^ 0 = x. After step 1, a encodes both values. Step 2 extracts the original a into b. Step 3 extracts the original b into a.

Practical note: In Python, just use a, b = b, a — it is clearer, faster (the compiler optimizes it), and works for any type. XOR swap is a classic interview question and useful in embedded systems where memory is extremely constrained, but it should never be used in production Python code.

def xor_swap(a, b):
    """Swap two integers using only XOR — no temp variable, no tuple unpacking.
    Return the swapped values as a tuple.
    """
    # Your code: three XOR operations
    pass
    return a, b
Expected Output
Before: a=42, b=17\nAfter:  a=17, b=42\nBefore: a=0, b=255\nAfter:  a=255, b=0\nBefore: a=-5, b=100\nAfter:  a=100, b=-5
Hints

Hint 1: The three steps are: a = a ^ b, then b = a ^ b, then a = a ^ b. After step 1, `a` holds the combined XOR of both original values.

Hint 2: XOR is its own inverse: if you XOR something twice with the same value, you get the original back. (x ^ y) ^ y = x.


Hard

#10Two's Complement EncoderHard
twos-complementnegative-numbersbit-representation

Implement twos_complement(value, num_bits) that returns the two's complement binary string representation of a signed integer in the given bit width.

The valid range for num_bits bits is -2^(num_bits-1) to 2^(num_bits-1) - 1. Raise ValueError if the value is out of range.

tests = [
(5, 8), (-5, 8), (0, 8), (-1, 8),
(127, 8), (-128, 8), (1, 4), (-8, 4),
]
for value, bits in tests:
print(f"{value:>4} in {bits} bits: {twos_complement(value, bits)}")
Solution
def twos_complement(value, num_bits):
min_val = -(1 << (num_bits - 1)) # -2^(n-1)
max_val = (1 << (num_bits - 1)) - 1 # 2^(n-1) - 1

if value < min_val or value > max_val:
raise ValueError(
f"{value} out of range for {num_bits}-bit two's complement "
f"[{min_val}, {max_val}]"
)

if value >= 0:
# Positive: just convert to binary and zero-pad
return format(value, f'0{num_bits}b')
else:
# Negative: compute 2^num_bits + value
unsigned = (1 << num_bits) + value
return format(unsigned, f'0{num_bits}b')

How two's complement works for -5 in 8 bits:

Method 1 — Invert and add 1:
+5 = 00000101
flip = 11111010
+1 = 11111011 ← this is -5

Method 2 — 2^n + value (used in our solution):
2^8 + (-5) = 256 - 5 = 251
251 = 11111011 ← same result

Why two's complement is universal:

  • Addition and subtraction use the same hardware circuit for both signed and unsigned numbers.
  • There is only one representation of zero (unlike sign-magnitude, which has +0 and -0).
  • The MSB (most significant bit) naturally indicates the sign: 0 = positive, 1 = negative.

Range for N bits: The leading bit is the sign bit, leaving N-1 bits for magnitude. Positive max = 2^(N-1) - 1, negative min = -2^(N-1). For 8 bits: -128 to 127.

def twos_complement(value, num_bits):
    """Return the two's complement binary string for a signed integer.
    
    Args:
        value: signed integer (can be negative)
        num_bits: bit width (e.g., 8 for a byte)
    
    Returns:
        Binary string of length num_bits
    
    Raises:
        ValueError if value is out of range for num_bits
    """
    pass
Expected Output
 5 in 8 bits: 00000101\n-5 in 8 bits: 11111011\n 0 in 8 bits: 00000000\n-1 in 8 bits: 11111111\n 127 in 8 bits: 01111111\n-128 in 8 bits: 10000000\n 1 in 4 bits: 0001\n-8 in 4 bits: 1000
Hints

Hint 1: For positive numbers, just convert to binary and zero-pad. For negative numbers, the two's complement is: flip all bits of the absolute value, then add 1.

Hint 2: Alternative shortcut: for negative values, compute 2^num_bits + value (which gives the unsigned representation of the negative number), then convert that to binary.

#11Bit Field Flag PackerHard
bit-fieldflagspackingsystems

Build a BitFlags class that packs multiple boolean flags into a single integer. This is how operating systems store file permissions, how network protocols pack header flags, and how game engines store entity states.

flags = BitFlags(['read', 'write', 'execute', 'admin'])

flags.set('read')
flags.set('execute')
print(f"After set read, execute: {flags.packed()} (0b{flags.packed():b})")
print(f"Active: {flags.active_flags()}")
print(f"read={flags.get('read')}, write={flags.get('write')}, "
f"execute={flags.get('execute')}, admin={flags.get('admin')}")

flags.toggle('execute')
print(f"After toggle execute: {flags.packed()} (0b{flags.packed():b})")

flags.clear('read')
print(f"After clear read: {flags.packed()} (0b{flags.packed():b})")

flags.unpack(0b1010)
print(f"Unpacked 0b1010: {flags.active_flags()}")
Solution
class BitFlags:
def __init__(self, flag_names):
self._positions = {name: i for i, name in enumerate(flag_names)}
self._names = flag_names
self._bits = 0

def set(self, name):
self._bits |= (1 << self._positions[name])

def clear(self, name):
self._bits &= ~(1 << self._positions[name])

def get(self, name):
return bool((self._bits >> self._positions[name]) & 1)

def toggle(self, name):
self._bits ^= (1 << self._positions[name])

def packed(self):
return self._bits

def unpack(self, value):
self._bits = value

def active_flags(self):
return [name for name in self._names if self.get(name)]

Bitwise operations used:

SET bit N: bits |= (1 << N) OR with mask → forces bit to 1
CLEAR bit N: bits &= ~(1 << N) AND with inverted mask → forces bit to 0
TOGGLE bit N: bits ^= (1 << N) XOR with mask → flips the bit
CHECK bit N: (bits >> N) & 1 shift right, then mask → 0 or 1

Real-world examples:

  • Unix file permissions: chmod 755 = owner rwx (111), group r-x (101), other r-x (101) = 0b111101101
  • TCP flags: SYN, ACK, FIN, RST, PSH, URG packed into a 6-bit field in the TCP header
  • CPU status registers: carry, zero, overflow, negative flags packed into a single register word

Why pack flags? A single 64-bit integer can hold 64 boolean flags in 8 bytes. Storing them as separate Python booleans would use 64 x 28 = 1,792 bytes (each Python bool object is 28 bytes). That is a 224x memory difference.

class BitFlags:
    """Pack multiple boolean flags into a single integer using bit fields.
    
    Example:
        flags = BitFlags(['read', 'write', 'execute', 'admin'])
        flags.set('read')
        flags.set('execute')
        flags.get('read')     # True
        flags.get('write')    # False
        flags.packed()        # 0b0101 = 5
    """

    def __init__(self, flag_names):
        """Each flag gets a bit position: first flag = bit 0, second = bit 1, etc."""
        pass

    def set(self, name):
        """Set a flag to True."""
        pass

    def clear(self, name):
        """Set a flag to False."""
        pass

    def get(self, name):
        """Return True if the flag is set."""
        pass

    def toggle(self, name):
        """Flip a flag."""
        pass

    def packed(self):
        """Return the integer holding all flags."""
        pass

    def unpack(self, value):
        """Load flags from a packed integer."""
        pass

    def active_flags(self):
        """Return list of flag names that are currently set."""
        pass
Expected Output
After set read, execute: 5 (0b101)\nActive: ['read', 'execute']\nread=True, write=False, execute=True, admin=False\nAfter toggle execute: 4 (0b100)\nAfter clear read: 0 (0b0)\nUnpacked 0b1010: ['write', 'admin']
Hints

Hint 1: Store flag names with their bit positions in a dict: {"read": 0, "write": 1, ...}. Use a single integer `self._bits` to hold all flags. Set bit N with `self._bits |= (1 << N)`.

Hint 2: Clear bit N with `self._bits &= ~(1 << N)`. Toggle with `self._bits ^= (1 << N)`. Check if set with `(self._bits >> N) & 1`.

#12IEEE 754 Float32 DecomposerHard
IEEE-754floating-pointstructbit-manipulation

Build a function decompose_float32(value) that takes a Python float, converts it to IEEE 754 single-precision (32-bit), and extracts the sign, exponent, and mantissa components.

Python
import struct

def decompose_float32(value):
    # Pack as float32, unpack as uint32 to get the raw bits
    packed = struct.pack('>f', value)
    bits = struct.unpack('>I', packed)[0]

    # Extract components
    binary_str = format(bits, '032b')
    sign = (bits >> 31) & 1
    exponent_bits = (bits >> 23) & 0xFF
    mantissa_bits = bits & 0x7FFFFF

    # Calculate actual values
    exponent_actual = exponent_bits - 127
    mantissa_value = 1.0
    for i in range(23):
        if mantissa_bits & (1 << (22 - i)):
            mantissa_value += 2 ** -(i + 1)

    result = (-1) ** sign * mantissa_value * (2 ** exponent_actual)

    print(f"Binary: {binary_str}")
    print(f"Sign: {sign} ({'+' if sign == 0 else '-'})")
    print(f"Exponent bits: {binary_str[1:9]} "
          f"(stored: {exponent_bits}, actual: {exponent_actual})")
    print(f"Mantissa bits: {binary_str[9:]} ({mantissa_value})")
    print(f"Formula: {'+' if sign == 0 else '-'}{mantissa_value} "
          f"* 2^{exponent_actual} = {result}")

print("=== 1.0 ===")
decompose_float32(1.0)
print()
print("=== -6.5 ===")
decompose_float32(-6.5)
print()
print("=== 0.1 ===")
decompose_float32(0.1)
Solution
import struct

def decompose_float32(value):
packed = struct.pack('>f', value)
bits = struct.unpack('>I', packed)[0]

binary_str = format(bits, '032b')
sign = (bits >> 31) & 1
exponent_bits = (bits >> 23) & 0xFF
mantissa_bits = bits & 0x7FFFFF

exponent_actual = exponent_bits - 127

# Reconstruct mantissa: 1.xxxxx (implicit leading 1)
mantissa_value = 1.0
for i in range(23):
if mantissa_bits & (1 << (22 - i)):
mantissa_value += 2 ** -(i + 1)

result = (-1) ** sign * mantissa_value * (2 ** exponent_actual)

print(f"Binary: {binary_str}")
print(f"Sign: {sign} ({'+' if sign == 0 else '-'})")
print(f"Exponent bits: {binary_str[1:9]} "
f"(stored: {exponent_bits}, actual: {exponent_actual})")
print(f"Mantissa bits: {binary_str[9:]} ({mantissa_value})")
print(f"Formula: {'+' if sign == 0 else '-'}{mantissa_value} "
f"* 2^{exponent_actual} = {result}")

IEEE 754 single-precision layout (32 bits):

| 1 bit | 8 bits | 23 bits |
| sign | exponent | mantissa |
| S | EEEEEEEE | MMMMMMMMMMMMMMMMMMMMMMM |

Worked example: -6.5

Step 1: Sign = 1 (negative)
Step 2: |6.5| = 110.1 in binary = 1.101 * 2^2
Step 3: Exponent = 2 + 127 (bias) = 129 = 10000001
Step 4: Mantissa = 101 (drop the leading 1, pad with zeros to 23 bits)

Result: 1 10000001 10100000000000000000000
S EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM

Why 0.1 is interesting: Its mantissa bits are 10011001100110011001101 — a repeating pattern that gets truncated, which is exactly why 0.1 + 0.2 != 0.3. The last bit is rounded up, introducing the tiny error.

The struct trick: struct.pack('>f', value) gives us the raw IEEE 754 bytes. struct.unpack('>I', ...) reinterprets those same bytes as an unsigned integer so we can do bit manipulation. This "type punning" is a systems programming technique used in network protocols, file formats, and hardware drivers.

import struct

def decompose_float32(value):
    """Decompose a Python float into its IEEE 754 single-precision components.
    
    Returns a dict with:
        'binary': full 32-bit binary string
        'sign': 0 or 1
        'exponent_bits': 8-bit string
        'exponent_value': actual exponent (after subtracting bias of 127)
        'mantissa_bits': 23-bit string
        'mantissa_value': actual mantissa as float (1.xxxxx in binary)
        'formula': string showing sign * mantissa * 2^exponent
    """
    pass
Expected Output
=== 1.0 ===\nBinary: 00111111100000000000000000000000\nSign: 0 (+)\nExponent bits: 01111111 (stored: 127, actual: 0)\nMantissa bits: 00000000000000000000000 (1.0)\nFormula: +1.0 * 2^0 = 1.0
Hints

Hint 1: Use `struct.pack(">f", value)` to get the raw 4 bytes of a float32, then `struct.unpack(">I", bytes)` to read those same bytes as an unsigned 32-bit integer. Now you can extract bits.

Hint 2: Sign = bit 31. Exponent = bits 30-23 (subtract bias 127). Mantissa = bits 22-0 (add implicit leading 1 for normal numbers). The formula is (-1)^sign * (1 + mantissa_fraction) * 2^(exponent - 127).

© 2026 EngineersOfAI. All rights reserved.