Coding Interview Patterns Module 17 – Bit manipulation
Bits across languages
Test, set, clear and toggle bits with masks, then see how Python, JavaScript, Java, C++ and Go differ on negative numbers, shifts and binary text.
What you will learn
- Use AND, OR, XOR, NOT and shifts to test, set, clear and toggle bits
- Predict negative numbers and shifts in Python, JavaScript, Java, C++ and Go
- Convert between bases and binary strings, including 32-bit patterns
- Avoid the precedence and bit-31 mistakes that small tests rarely catch
Before you start
What you’ll learn · Before you start
What you will learn
- Use AND, OR, XOR, NOT and shifts to test, set, clear and toggle bits
- Predict negative numbers and shifts in Python, JavaScript, Java, C++ and Go
- Convert between bases and binary strings, including 32-bit patterns
- Avoid the precedence and bit-31 mistakes that small tests rarely catch
Before you start
On this page
An integer is a row of bits, so one 32-bit integer can hold 32 yes-or-no facts, one per bit. Four expressions do
most of the work, and they look the same in Python, JavaScript, Java, C++ and Go. With the mask 1 << i, which has
only bit i on, x & (1 << i) tests bit i, x | (1 << i) sets it, x & ~(1 << i) clears it and x ^ (1 << i)
toggles it. Go writes NOT as ^x and has an extra operator, &^, for clearing; the rest is spelled the same.
The integers underneath are not the same. Python’s integers have no fixed width, JavaScript turns its numbers into
32-bit integers for every bitwise operator, Java’s int and long are always signed, and C++ and Go have unsigned
types as well. So one expression can give different answers in different languages for a negative number, for bit
31, for a shift by 32 places and for binary text. This lesson runs the same small programs in all five languages and
shows what each one printed, so that you know your language’s answer before a hidden test finds it for you.
Masks and the four moves
Number the bits from the right, starting at 0, so that bit i is worth 2ⁱ. In eight bits, 106 is 0110 1010,
because 106 = 64 + 32 + 8 + 2: bits 6, 5, 3 and 1 are on. A mask is a number whose 1 bits mark the positions you
want to work on, and 1 << i is the mask of bit i alone. Six operators work on the bits of integers:
a & b(AND) has a 1 wherever bothaandbhave a 1.a | b(OR) has a 1 wherever at least one of them has a 1.a ^ b(XOR) has a 1 wherever the two differ.~a(NOT, written^ain Go) flips every bit ofa.a << kmoves every bitkplaces to the left and fills the right with zeros; as long as the result still fits the type, that multipliesaby 2ᵏ.a >> kmoves every bitkplaces to the right, so the lowestkbits fall off; that dividesaby 2ᵏ and rounds down.
A mask of one bit and these operators give the four moves:
- Test:
x & (1 << i)keeps bitiand turns every other bit off, so the result is 0 exactly when bitiis off. - Set:
x | (1 << i)turns bition. - Clear:
x & ~(1 << i)turns bitioff. - Toggle:
x ^ (1 << i)flips biti.
Set, clear and toggle leave every other bit as it was.
The four moves on x = 106, each with a mask of one bit
Text description of the diagram
The diagram shows eight bit positions, numbered 7 on the left down to 0 on the right, and five rows of eight cells. The last column gives each row's value in decimal. In each move, the cell of the bit the mask touches is drawn with a thicker border.
- x = 106: 0110 1010, so bits 6, 5, 3 and 1 are on (64 + 32 + 8 + 2).
- Test bit 3 with x & (1 << 3): 0000 1000, which is 8. Every bit except bit 3 is 0 in the mask, so the result keeps only bit 3 of x; it is not 0, so bit 3 is on.
- Set bit 2 with x | (1 << 2): 0110 1110, which is 110. Bit 2 is now on and the other bits are unchanged.
- Clear bit 5 with x & ~(1 << 5): 0100 1010, which is 74. Bit 5 is now off and the other bits are unchanged.
- Toggle bit 0 with x ^ (1 << 0): 0110 1011, which is 107. Bit 0 flipped from 0 to 1; toggling it again would give 106 back.
"""The four moves on one byte: test, set, clear and toggle a single bit of x."""
def byte(n):
"""n as 8 binary digits, in two groups of four."""
digits = format(n, "08b")
return digits[:4] + " " + digits[4:]
x = 106
print(f"x {byte(x)} {x:>3}")
print(f"x & (1 << 3) {byte(x & (1 << 3))} {x & (1 << 3):>3} test bit 3: not 0, so it is set")
print(f"x | (1 << 2) {byte(x | (1 << 2))} {x | (1 << 2):>3} set bit 2")
print(f"x & ~(1 << 5) {byte(x & ~(1 << 5))} {x & ~(1 << 5):>3} clear bit 5")
print(f"x ^ (1 << 0) {byte(x ^ (1 << 0))} {x ^ (1 << 0):>3} toggle bit 0")
print(f"(x >> 3) & 1 {'':9} {(x >> 3) & 1:>3} test bit 3 as 0 or 1") Output
x 0110 1010 106 x & (1 << 3) 0000 1000 8 test bit 3: not 0, so it is set x | (1 << 2) 0110 1110 110 set bit 2 x & ~(1 << 5) 0100 1010 74 clear bit 5 x ^ (1 << 0) 0110 1011 107 toggle bit 0 (x >> 3) & 1 1 test bit 3 as 0 or 1
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 four_moves.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The test does not give 0 or 1. It gives 0 or 2ⁱ, the value of the bit it kept: 8 for bit 3. So compare it with
zero, (x & (1 << i)) != 0, or move the bit down to position 0 first: (x >> i) & 1 is exactly 0 or 1. The clear
works because ~(1 << i) has every bit on except bit i, so the AND keeps everything else as it was.
One feature-flag mask in five languages
A notes app keeps each user’s settings as the bits of one 32-bit mask: dark mode is bit 0, email alerts bit 1, SMS alerts bit 2, two-factor sign-in bit 3, and early access to beta features bit 31. The program below sets three flags, clears one, toggles another, then lists the flags that are on, counts them and finds the highest. You can run the Python and JavaScript tabs on this page:
Python · flags/flags.py
"""One user's settings kept in a single 32-bit mask: each bit position is one flag."""
DARK_MODE, EMAIL_ALERTS, SMS_ALERTS, TWO_FACTOR, BETA = 0, 1, 2, 3, 31
NAMES = {DARK_MODE: "dark mode", EMAIL_ALERTS: "email alerts", SMS_ALERTS: "SMS alerts", TWO_FACTOR: "two-factor", BETA: "beta"}
def show(step, flags):
print(f"{step:<22}{flags:>12} {flags:032b}")
flags = 0
flags |= 1 << DARK_MODE # set a bit: OR with its mask
flags |= 1 << SMS_ALERTS
flags |= 1 << BETA
show("set dark, SMS, beta", flags)
flags &= ~(1 << SMS_ALERTS) # clear a bit: AND with the mask inverted
show("clear SMS", flags)
flags ^= 1 << EMAIL_ALERTS # toggle a bit: XOR with its mask
show("toggle email", flags)
on = [name for bit, name in NAMES.items() if (flags >> bit) & 1]
print(f"on: {', '.join(on)}")
print(f"flags on: {flags.bit_count()} highest bit on: {flags.bit_length() - 1}")
print(f"beta tested with > 0: {(flags & (1 << BETA)) > 0}") Output
set dark, SMS, beta 2147483653 10000000000000000000000000000101 clear SMS 2147483649 10000000000000000000000000000001 toggle email 2147483651 10000000000000000000000000000011 on: dark mode, email alerts, beta flags on: 3 highest bit on: 31 beta tested with > 0: True
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 flags.py
JavaScript · flags/flags.mjs
// One user's settings kept in a single 32-bit mask: each bit position is one flag.
const DARK_MODE = 0, EMAIL_ALERTS = 1, SMS_ALERTS = 2, TWO_FACTOR = 3, BETA = 31;
const NAMES = new Map([[DARK_MODE, "dark mode"], [EMAIL_ALERTS, "email alerts"], [SMS_ALERTS, "SMS alerts"], [TWO_FACTOR, "two-factor"], [BETA, "beta"]]);
function show(step, flags) {
const bits = (flags >>> 0).toString(2).padStart(32, "0"); // >>> 0 reads the 32 bits as unsigned
console.log(`${step.padEnd(22)}${String(flags).padStart(12)} ${bits}`);
}
let flags = 0;
flags |= 1 << DARK_MODE; // set a bit: OR with its mask
flags |= 1 << SMS_ALERTS;
flags |= 1 << BETA;
show("set dark, SMS, beta", flags);
flags &= ~(1 << SMS_ALERTS); // clear a bit: AND with the mask inverted
show("clear SMS", flags);
flags ^= 1 << EMAIL_ALERTS; // toggle a bit: XOR with its mask
show("toggle email", flags);
const on = [...NAMES].filter(([bit]) => (flags >>> bit) & 1).map(([, name]) => name);
console.log(`on: ${on.join(", ")}`);
let count = 0;
for (let bit = 0; bit < 32; bit++) count += (flags >>> bit) & 1; // JavaScript has no built-in bit count
console.log(`flags on: ${count} highest bit on: ${31 - Math.clz32(flags)}`);
console.log(`beta tested with > 0: ${(flags & (1 << BETA)) > 0}`); Output
set dark, SMS, beta -2147483643 10000000000000000000000000000101 clear SMS -2147483647 10000000000000000000000000000001 toggle email -2147483645 10000000000000000000000000000011 on: dark mode, email alerts, beta flags on: 3 highest bit on: 31 beta tested with > 0: false
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node flags.mjs
Java · compiled/Flags.java
// One user's settings kept in a single 32-bit mask: each bit position is one flag.
import java.util.StringJoiner;
public class Flags {
static final int DARK_MODE = 0, EMAIL_ALERTS = 1, SMS_ALERTS = 2, TWO_FACTOR = 3, BETA = 31;
static final int[] BITS = {DARK_MODE, EMAIL_ALERTS, SMS_ALERTS, TWO_FACTOR, BETA};
static final String[] NAMES = {"dark mode", "email alerts", "SMS alerts", "two-factor", "beta"};
static void show(String step, int flags) {
String bits = String.format("%32s", Integer.toBinaryString(flags)).replace(' ', '0');
System.out.printf("%-22s%12d %s%n", step, flags, bits);
}
public static void main(String[] args) {
int flags = 0;
flags |= 1 << DARK_MODE; // set a bit: OR with its mask
flags |= 1 << SMS_ALERTS;
flags |= 1 << BETA;
show("set dark, SMS, beta", flags);
flags &= ~(1 << SMS_ALERTS); // clear a bit: AND with the mask inverted
show("clear SMS", flags);
flags ^= 1 << EMAIL_ALERTS; // toggle a bit: XOR with its mask
show("toggle email", flags);
StringJoiner on = new StringJoiner(", ");
for (int k = 0; k < BITS.length; k++) {
if (((flags >>> BITS[k]) & 1) != 0) on.add(NAMES[k]);
}
System.out.println("on: " + on);
System.out.println("flags on: " + Integer.bitCount(flags) + " highest bit on: " + (31 - Integer.numberOfLeadingZeros(flags)));
System.out.println("beta tested with > 0: " + ((flags & (1 << BETA)) > 0));
}
} C++ · compiled/flags.cpp
// One user's settings kept in a single 32-bit mask: each bit position is one flag.
#include <bit>
#include <cstdint>
#include <print>
#include <string>
#include <utility>
#include <vector>
constexpr int DARK_MODE = 0, EMAIL_ALERTS = 1, SMS_ALERTS = 2, TWO_FACTOR = 3, BETA = 31;
const std::vector<std::pair<int, std::string>> NAMES = {
{DARK_MODE, "dark mode"}, {EMAIL_ALERTS, "email alerts"}, {SMS_ALERTS, "SMS alerts"}, {TWO_FACTOR, "two-factor"}, {BETA, "beta"}};
void show(const std::string& step, std::uint32_t flags) {
std::println("{:<22}{:>12} {:032b}", step, flags, flags);
}
int main() {
std::uint32_t flags = 0; // unsigned: bit 31 is just another bit
flags |= 1u << DARK_MODE; // set a bit: OR with its mask
flags |= 1u << SMS_ALERTS;
flags |= 1u << BETA;
show("set dark, SMS, beta", flags);
flags &= ~(1u << SMS_ALERTS); // clear a bit: AND with the mask inverted
show("clear SMS", flags);
flags ^= 1u << EMAIL_ALERTS; // toggle a bit: XOR with its mask
show("toggle email", flags);
std::string on;
for (const auto& [bit, name] : NAMES) {
if ((flags >> bit) & 1u) on += (on.empty() ? "" : ", ") + name;
}
std::println("on: {}", on);
std::println("flags on: {} highest bit on: {}", std::popcount(flags), std::bit_width(flags) - 1);
std::println("beta tested with > 0: {}", (flags & (1u << BETA)) > 0);
} Go · compiled/flags.go
// One user's settings kept in a single 32-bit mask: each bit position is one flag.
package main
import (
"fmt"
"math/bits"
"strings"
)
const (
DarkMode = 0
EmailAlerts = 1
SMSAlerts = 2
TwoFactor = 3
Beta = 31
)
var names = []struct {
bit int
name string
}{{DarkMode, "dark mode"}, {EmailAlerts, "email alerts"}, {SMSAlerts, "SMS alerts"}, {TwoFactor, "two-factor"}, {Beta, "beta"}}
func show(step string, flags uint32) {
fmt.Printf("%-22s%12d %032b\n", step, flags, flags)
}
func main() {
var flags uint32 // unsigned: bit 31 is just another bit
flags |= 1 << DarkMode // set a bit: OR with its mask
flags |= 1 << SMSAlerts
flags |= 1 << Beta
show("set dark, SMS, beta", flags)
flags &^= 1 << SMSAlerts // clear a bit: Go's AND NOT operator
show("clear SMS", flags)
flags ^= 1 << EmailAlerts // toggle a bit: XOR with its mask
show("toggle email", flags)
var on []string
for _, f := range names {
if (flags>>f.bit)&1 != 0 {
on = append(on, f.name)
}
}
fmt.Println("on:", strings.Join(on, ", "))
fmt.Printf("flags on: %d highest bit on: %d\n", bits.OnesCount32(flags), bits.Len32(flags)-1)
fmt.Println("beta tested with > 0:", flags&(1<<Beta) > 0)
} Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
The Java, C++ and Go versions are built and run as you would in a terminal. The last command shows two masks that Go refuses to compile:
from session import run, versions
# Flags.java runs as a single source file, flags.cpp is built with the sanitizers on, and go run builds flags.go.
# A line that starts with "!" must fail: mask_types.go holds two masks that Go refuses to compile.
versions("java -version", "clang++ --version", "go version")
run("""
java Flags.java
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined flags.cpp -o flags
./flags
go run flags.go
!go run mask_types.go
""") Output
# openjdk version "25.0.4.1" LTS # Apple clang version 21.0.0 (clang-2100.3.34.2) # go version go1.27.1 darwin/arm64 $ java Flags.java set dark, SMS, beta -2147483643 10000000000000000000000000000101 clear SMS -2147483647 10000000000000000000000000000001 toggle email -2147483645 10000000000000000000000000000011 on: dark mode, email alerts, beta flags on: 3 highest bit on: 31 beta tested with > 0: false $ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined flags.cpp -o flags $ ./flags set dark, SMS, beta 2147483653 10000000000000000000000000000101 clear SMS 2147483649 10000000000000000000000000000001 toggle email 2147483651 10000000000000000000000000000011 on: dark mode, email alerts, beta flags on: 3 highest bit on: 31 beta tested with > 0: true $ go run flags.go set dark, SMS, beta 2147483653 10000000000000000000000000000101 clear SMS 2147483649 10000000000000000000000000000001 toggle email 2147483651 10000000000000000000000000000011 on: dark mode, email alerts, beta flags on: 3 highest bit on: 31 beta tested with > 0: true $ go run mask_types.go # command-line-arguments ./mask_types.go:8:11: 1 << 31 (untyped int constant 2147483648) overflows int32 ./mask_types.go:11:14: ^(1 << 2) (untyped int constant -5) overflows uint32 [exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 flags_session.py
The last column, the 32 bits, is the same in every language, and so are the names of the flags that are on. The
decimal number is not: 2147483651 in Python, C++ and Go, but −2147483645 in JavaScript and Java. Both are right.
They are the same 32 bits read in two ways, as an unsigned number and as a signed one, where bit 31 counts as
−2³¹ instead of +2³¹. Python’s integer simply grows to 2³¹ + 3. The C++ and Go versions use the unsigned types
std::uint32_t and uint32, in which bit 31 is just another bit. JavaScript’s bitwise operators, except >>>,
return a signed 32-bit result, and Java’s int is signed.
That difference breaks one common test. In JavaScript and Java, (flags & (1 << 31)) > 0 is false even though the
beta flag is on, because the result is −2147483648. Compare with zero instead: != 0 (!== in JavaScript) works
for every bit in every language. The same goes for a test that reads a bit back with >>: in Java and JavaScript,
flags >> 31 copies the sign bit and gives −1, while flags >>> 31 gives 1.
The counts use each language’s built-ins: bit_count() (Python 3.10 and later) and bit_length() in Python,
Integer.bitCount and Integer.numberOfLeadingZeros in Java, std::popcount and std::bit_width from C++’s
<bit>, and bits.OnesCount32 and bits.Len32 from Go’s math/bits. JavaScript has no bit count, so its version
loops over the 32 bits, and Math.clz32 counts the leading zero bits. The C++ and Go helpers accept unsigned types
only: the C++ standard makes that a constraint of every counting function in <bit>, and math/bits works on Go’s
unsigned types. That is one more reason to keep a mask in std::uint32_t or uint32.
Go checks constants against the type they are used as. An int32 mask cannot take 1 << 31, because 2147483648 is
one more than the largest int32, and ^(1 << 2) is the untyped constant −5, which no uint32 can hold. Both are
compile errors, which is better than a wrong answer: use uint32 masks, and clear bits with &^.
Negative numbers are two’s complement
In all five languages, the bitwise operators see a negative integer in two’s complement. In n bits, −x is
stored as 2ⁿ − x, so in eight bits −5 is 256 − 5 = 251, which is 1111 1011. Read the other way, the top bit counts
as −2ⁿ⁻¹ instead of +2ⁿ⁻¹: −128 + 64 + 32 + 16 + 8 + 2 + 1 = −5. Two facts follow in every language. −1 has every
bit on, and ~x equals -x - 1, so ~5 is −6; the Java Language Specification states that rule for ~ in those
words.
Python has no fixed width, so how many bits does −5 have? Its documentation defines the bitwise operators as if every
negative number had infinitely many sign bits: an endless row of 1s to the left of 1011. The program shows what
that means in practice:
"""Negative numbers in Python: two's complement with an endless row of sign bits on the left."""
def to_int32(u):
"""The low 32 bits of u, read as a signed 32-bit integer (what a Java int or a JavaScript bitwise result holds)."""
u &= 0xFFFFFFFF
return u - (1 << 32) if u >> 31 else u
x = -5
print("bits 7 to 0 of -5 ", "".join(str((x >> i) & 1) for i in range(7, -1, -1)))
print("bit 40 of -5 ", (x >> 40) & 1)
print("bin(-5) ", bin(x))
print("-5 & 0xFF ", x & 0xFF, "=", format(x & 0xFF, "08b"))
print("-5 & 0xFFFFFFFF ", x & 0xFFFFFFFF)
print("~5 and ~-6 ", ~5, ~-6)
print("-1 >> 1, -1 >> 99 ", -1 >> 1, -1 >> 99)
for u in (4294967291, 1 << 31, (1 << 31) - 1, 1 << 32):
print(f"to_int32({u})".ljust(20), to_int32(u)) Output
bits 7 to 0 of -5 11111011 bit 40 of -5 1 bin(-5) -0b101 -5 & 0xFF 251 = 11111011 -5 & 0xFFFFFFFF 4294967291 ~5 and ~-6 -6 5 -1 >> 1, -1 >> 99 -1 -1 to_int32(4294967291) -5 to_int32(2147483648) -2147483648 to_int32(2147483647) 2147483647 to_int32(4294967296) 0
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 negatives.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
- Every bit of −5 above bit 2 is 1, bit 40 included, and
-1 >> 99is still −1: shifting right keeps feeding in copies of the sign bit. bin(-5)prints-0b101, a minus sign and the magnitude, not the bits as stored. To see the bits, choose a width and keep only those bits:-5 & 0xFFis 251, the eight-bit pattern, and-5 & 0xFFFFFFFFis 4294967291, the 32-bit one.- To read 32 bits back as a signed number, the way Java or JavaScript would, subtract 2³² when bit 31 is on.
to_int32does that; it is the step to take whenever a Python answer must match a 32-bit language.
The exercise at the end of this lesson puts the four moves and this conversion together.
Shifts: where the five languages part ways
Right shifts of negative numbers, shifts that fill with zeros, and shifts by the full width are where the five
languages disagree. Each program below prints the same rows, written in its own language. C++ moves the shift by 32
into a program of its own, and Go adds a row for its 64-bit int:
Python · shifts/shifts.py
"""Shifting a negative number, and shifting 1 by 31 and 32 places, in Python."""
x = -7
rows = [
("-7 >> 1", x >> 1), # right shift rounds down
("-7 // 2", x // 2), # so does //, Python's integer division
("-7 >>> 1 (32 bits)", (x & 0xFFFFFFFF) >> 1), # no >>> operator: mask to 32 bits, then shift
("1 << 31", 1 << 31),
("1 << 32", 1 << 32),
]
for label, value in rows:
print(f"{label:<20}{value}") Output
-7 >> 1 -4 -7 // 2 -4 -7 >>> 1 (32 bits) 2147483644 1 << 31 2147483648 1 << 32 4294967296
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shifts.py
JavaScript · shifts/shifts.mjs
// Shifting a negative number, and shifting 1 by 31 and 32 places, in JavaScript.
const x = -7;
const rows = [
["-7 >> 1", x >> 1], // right shift rounds down
["-7 / 2", Math.trunc(x / 2)], // integer division rounds towards zero
["-7 >>> 1 (32 bits)", x >>> 1], // zero-filling right shift
["1 << 31", 1 << 31],
["1 << 32", 1 << 32], // the count is taken modulo 32
];
for (const [label, value] of rows) console.log(`${label.padEnd(20)}${value}`); Output
-7 >> 1 -4 -7 / 2 -3 -7 >>> 1 (32 bits) 2147483644 1 << 31 -2147483648 1 << 32 1
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node shifts.mjs
Java · compiled/Shifts.java
// Shifting a negative number, and shifting 1 by 31 and 32 places, in Java.
public class Shifts {
static void row(String label, int value) {
System.out.printf("%-20s%d%n", label, value);
}
public static void main(String[] args) {
int x = -7, one = 1, count = 32;
row("-7 >> 1", x >> 1); // right shift rounds down
row("-7 / 2", x / 2); // integer division rounds towards zero
row("-7 >>> 1 (32 bits)", x >>> 1); // zero-filling right shift
row("1 << 31", one << 31);
row("1 << 32", one << count); // an int shift uses only the low 5 bits of the count
}
} C++ · compiled/shifts.cpp
// Shifting a negative number, and shifting 1 by 31 places, in C++ (shift32.cpp tries 32).
#include <cstdint>
#include <print>
void row(const char* label, auto value) {
std::println("{:<20}{}", label, value);
}
int main() {
int x = -7, one = 1;
row("-7 >> 1", x >> 1); // right shift rounds down (defined since C++20)
row("-7 / 2", x / 2); // integer division rounds towards zero
row("-7 >>> 1 (32 bits)", static_cast<std::uint32_t>(x) >> 1); // no >>>: shift the unsigned value
row("1 << 31", one << 31); // the 1 moves into the sign bit: the lowest int
} C++, by 32 · compiled/shift32.cpp
// Shifting a 32-bit int by 32 places: undefined behaviour in every version of C++.
#include <print>
int main() {
int one = 1, count = 32;
std::println("1 << 32 = {}", one << count);
} Go · compiled/shifts.go
// Shifting a negative number, and shifting 1 by 31 and 32 places, in Go.
package main
import "fmt"
func row(label string, value any) {
fmt.Printf("%-20s%v\n", label, value)
}
func main() {
var x, one int32 = -7, 1
count := 32
row("-7 >> 1", x>>1) // right shift rounds down
row("-7 / 2", x/2) // integer division rounds towards zero
row("-7 >>> 1 (32 bits)", uint32(x)>>1) // no >>>: shift the unsigned value
row("1 << 31", one<<31) // int32 wraps around
row("1 << 32", one<<count) // no limit on the count: every bit is shifted out
row("1 << 32 as int", 1<<count) // a plain int has 64 bits here
} Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
from session import run, versions
# A line that starts with "!" must fail: shift32.cpp is built with -fno-sanitize-recover=undefined, so the
# sanitizer stops the program at the shift.
versions("java -version", "clang++ --version", "go version")
run("""
java Shifts.java
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined shifts.cpp -o shifts
./shifts
clang++ -std=c++23 -Wall -Wextra -fsanitize=undefined -fno-sanitize-recover=undefined shift32.cpp -o shift32
!./shift32
go run shifts.go
""") Output
# openjdk version "25.0.4.1" LTS # Apple clang version 21.0.0 (clang-2100.3.34.2) # go version go1.27.1 darwin/arm64 $ java Shifts.java -7 >> 1 -4 -7 / 2 -3 -7 >>> 1 (32 bits) 2147483644 1 << 31 -2147483648 1 << 32 1 $ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined shifts.cpp -o shifts $ ./shifts -7 >> 1 -4 -7 / 2 -3 -7 >>> 1 (32 bits) 2147483644 1 << 31 -2147483648 $ clang++ -std=c++23 -Wall -Wextra -fsanitize=undefined -fno-sanitize-recover=undefined shift32.cpp -o shift32 $ ./shift32 shift32.cpp:6:38: runtime error: shift exponent 32 is too large for 32-bit type 'int' SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior shift32.cpp:6:38 [exit status 134] $ go run shifts.go -7 >> 1 -4 -7 / 2 -3 -7 >>> 1 (32 bits) 2147483644 1 << 31 -2147483648 1 << 32 0 1 << 32 as int 4294967296
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shifts_session.py
The recorded outputs, row by row (Go’s values are for int32):
-7 >> 1: −4 in all five languages.- −7 divided by 2: −4 in Python, whose
//rounds down; −3 in JavaScript, Java, C++ and Go. -7 >>> 1on 32 bits: 2147483644 in all five, although only JavaScript and Java have a>>>operator.1 << 31: 2147483648 in Python; −2147483648 in JavaScript, Java, C++ and Go.1 << 32: 4294967296 in Python; 1 in JavaScript and Java; 0 in Go; undefined behaviour in C++.
Right shifts of negative numbers round down in all five: -7 >> 1 is −4, because the sign bit is copied in from
the left. Integer division rounds towards zero instead, so in JavaScript, Java, C++ and Go -7 / 2 is −3 and
differs from -7 >> 1. Python’s // rounds down too, so there the two agree. Replace a division by a shift only
when the value cannot be negative.
A shift that fills with zeros treats the number as unsigned. Java and JavaScript have an operator for it, >>>;
in JavaScript it also converts its operand to an unsigned 32-bit number, which is why x >>> 0 is the usual way to
read a value’s 32 bits as unsigned. C++ and Go have no >>>: shift an unsigned type, which their languages define
as a logical shift. Python masks first: (x & 0xFFFFFFFF) >> 1.
Shifting by the full width gives three different answers, and none at all in C++:
- JavaScript takes the shift count modulo 32, and Java uses only the five lowest bits of the count for an
int(six for along), so in both a shift by 32 is a shift by 0 and1 << 32is 1. - Go has no upper limit on the count: shifting an
int3232 places moves every bit out, and the result is 0. A plain Gointhas 64 bits on 64-bit platforms such as this one, which is why the last row of the Go output is 4294967296. - In C++ a shift count that is negative, or not less than the width of the left operand, is undefined behaviour in
every version of the language. Built with
-fsanitize=undefined -fno-sanitize-recover=undefined, Clang’s sanitizer stops the program at that shift and names its line; without the sanitizer, the standard places no requirement on what the program does, so a wrong number is the mildest outcome. - Python’s integers have no width, so
1 << 32is simply 2³².
Version note
C++20 made two’s complement the only representation of signed integers and defined both shifts on them: >> of a
negative value rounds down, and << keeps the low bits of the exact result, so 1 << 31 on an int is the
smallest int and -1 << 1 is −2. Before C++20, right-shifting a negative value was implementation-defined and
left-shifting one was undefined. A shift count that is negative, or not less than the width, is undefined in every
version.
The rule that keeps you safe in every language is short: keep the shift count below the width of the value you
shift, and build masks wider than 32 bits from a 64-bit one: 1L << k in Java, 1ULL << k or
std::uint64_t{1} << k in C++, a uint64 in Go, and BigInt values such as 1n << 40n in JavaScript, whose bitwise
operators otherwise never go past 32 bits.
Precedence: parenthesise every bit expression
Two expressions that appear in real solutions, x & 1 == 0 (is x even?) and 1 << i + 1, are read differently
from language to language. Predict what each version prints before you look:
Python · precedence/precedence.py
"""Two bit expressions without parentheses, as Python reads them."""
x, i = 6, 2
print("x & 1 == 0 ", x & 1 == 0) # meant: is x even?
print("1 << i + 1 ", 1 << i + 1) # meant: the mask of bit i + 1 Output
x & 1 == 0 True 1 << i + 1 8
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 precedence.py
JavaScript · precedence/precedence.mjs
// Two bit expressions without parentheses, as JavaScript reads them.
const x = 6, i = 2;
console.log(`x & 1 == 0 ${x & 1 == 0}`); // meant: is x even?
console.log(`1 << i + 1 ${1 << i + 1}`); // meant: the mask of bit i + 1 Output
x & 1 == 0 0 1 << i + 1 8
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node precedence.mjs
Java: AND · compiled/AndPrecedence.java
// x & 1 == 0 without parentheses, as Java reads it.
public class AndPrecedence {
public static void main(String[] args) {
int x = 6;
System.out.println("x & 1 == 0 " + (x & 1 == 0)); // meant: is x even?
}
} Java: shift · compiled/ShiftPrecedence.java
// 1 << i + 1 without parentheses, as Java reads it.
public class ShiftPrecedence {
public static void main(String[] args) {
int i = 2;
System.out.println("1 << i + 1 " + (1 << i + 1)); // meant: the mask of bit i + 1
}
} C++ · compiled/precedence.cpp
// Two bit expressions without parentheses, as C++ reads them.
#include <print>
int main() {
int x = 6, i = 2;
std::println("x & 1 == 0 {}", x & 1 == 0); // meant: is x even?
std::println("1 << i + 1 {}", 1 << i + 1); // meant: the mask of bit i + 1
} Go · compiled/precedence.go
// Two bit expressions without parentheses, as Go reads them.
package main
import "fmt"
func main() {
x, i := 6, 2
fmt.Println("x & 1 == 0 ", x&1 == 0) // meant: is x even?
fmt.Println("1 << i + 1 ", 1<<i+1) // meant: the mask of bit i + 1
} Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
from session import run, versions
# A line that starts with "!" must fail: javac rejects AndPrecedence.java. -fno-caret-diagnostics keeps each
# compiler message on one line, without the source excerpt under it.
versions("java -version", "clang++ --version", "go version")
run("""
!java AndPrecedence.java
java ShiftPrecedence.java
clang++ -std=c++23 -Wall -Wextra -fno-caret-diagnostics precedence.cpp -o precedence
./precedence
go run precedence.go
""") Output
# openjdk version "25.0.4.1" LTS
# Apple clang version 21.0.0 (clang-2100.3.34.2)
# go version go1.27.1 darwin/arm64
$ java AndPrecedence.java
AndPrecedence.java:5: error: bad operand types for binary operator '&'
System.out.println("x & 1 == 0 " + (x & 1 == 0)); // meant: is x even?
^
first type: int
second type: boolean
1 error
error: compilation failed
[exit status 1]
$ java ShiftPrecedence.java
1 << i + 1 8
$ clang++ -std=c++23 -Wall -Wextra -fno-caret-diagnostics precedence.cpp -o precedence
precedence.cpp:6:39: warning: & has lower precedence than ==; == will be evaluated first [-Wparentheses]
precedence.cpp:6:39: note: place parentheses around the '==' expression to silence this warning
precedence.cpp:6:39: note: place parentheses around the & expression to evaluate it first
precedence.cpp:7:44: warning: operator '<<' has lower precedence than '+'; '+' will be evaluated first [-Wshift-op-parentheses]
precedence.cpp:7:44: note: place parentheses around the '+' expression to silence this warning
$ ./precedence
x & 1 == 0 0
1 << i + 1 8
$ go run precedence.go
x & 1 == 0 true
1 << i + 1 5
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 precedence_session.py
With x = 6 and i = 2, the recorded outputs are:
x & 1 == 0:Truein Python andtruein Go; 0 in JavaScript; 0 in C++, with a warning; a compile error in Java.1 << i + 1: 8 in Python, JavaScript and Java; 8 in C++, with a warning; 5 in Go.
In JavaScript, Java and C++, == binds more tightly than &, so the first expression means x & (1 == 0).
JavaScript turns false into 0 and quietly gives 0, which reads as “not even” for an even number. Java refuses to
apply & to an int and a boolean, and C++ compiles it, with a warning from Clang. Python ranks comparisons
below the bitwise operators, and Go puts & beside *, so both read (x & 1) == 0. The shift goes the other way:
everywhere except Go, << binds more loosely than +, so 1 << i + 1 is 1 << 3; Go ranks << with * and reads
(1 << i) + 1.
You do not need to remember five precedence tables. Put parentheses around every bitwise operation that sits next to a
comparison or an arithmetic operator: (x & 1) == 0, (x & mask) != 0, 1 << (i + 1). Then every language reads
the expression the way you meant it. The -fno-caret-diagnostics flag in the C++ command only keeps each compiler
message on one line.
Binary strings and other bases
Turning a number into binary text, and back, is the last place where the five languages differ. A negative number is the test case that shows it:
Python · bases/bases.py
"""Binary text in Python: signed digits, a 32-bit pattern, and the way back."""
x = -5
bits = format(x & 0xFFFFFFFF, "032b") # the 32-bit pattern of x
u = int(bits, 2) # int() reads the pattern as an unsigned number
rows = [
("bin(x)", bin(x)),
('format(x, "b")', format(x, "b")),
("32-bit pattern", bits),
("int(bits, 2)", u),
("back to signed", (u ^ 0x80000000) - 0x80000000),
('int("1011", 2)', int("1011", 2)),
('int("0b1011", 0)', int("0b1011", 0)),
("hex(255)", hex(255)),
]
for label, value in rows:
print(f"{label:<18}{value}") Output
bin(x) -0b101
format(x, "b") -101
32-bit pattern 11111111111111111111111111111011
int(bits, 2) 4294967291
back to signed -5
int("1011", 2) 11
int("0b1011", 0) 11
hex(255) 0xff
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bases.py
JavaScript · bases/bases.mjs
// Binary text in JavaScript: signed digits, a 32-bit pattern, and the way back.
const x = -5;
const bits = (x >>> 0).toString(2).padStart(32, "0"); // the 32-bit pattern of x
const rows = [
["x.toString(2)", x.toString(2)],
["32-bit pattern", bits],
["parseInt(bits, 2)", parseInt(bits, 2)], // read as an unsigned number
["back to signed", parseInt(bits, 2) | 0], // | 0 converts to a signed 32-bit integer
['parseInt("1011", 2)', parseInt("1011", 2)],
['Number("0b1011")', Number("0b1011")],
["(255).toString(16)", (255).toString(16)],
];
for (const [label, value] of rows) console.log(`${label.padEnd(21)}${value}`); Output
x.toString(2) -101
32-bit pattern 11111111111111111111111111111011
parseInt(bits, 2) 4294967291
back to signed -5
parseInt("1011", 2) 11
Number("0b1011") 11
(255).toString(16) ff
Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node bases.mjs
Java · compiled/Bases.java
// Binary text in Java: signed digits, a 32-bit pattern, and the way back.
public class Bases {
static void row(String label, Object value) {
System.out.printf("%-35s%s%n", label, value);
}
public static void main(String[] args) {
int x = -5;
String bits = Integer.toBinaryString(x); // 32 digits here; pad with zeros when bit 31 is 0
row("Integer.toString(x, 2)", Integer.toString(x, 2));
row("Integer.toBinaryString(x)", bits);
try {
row("Integer.parseInt(bits, 2)", Integer.parseInt(bits, 2));
} catch (NumberFormatException e) {
row("Integer.parseInt(bits, 2)", "NumberFormatException"); // 4294967291 does not fit an int
}
row("Integer.parseUnsignedInt(bits, 2)", Integer.parseUnsignedInt(bits, 2));
row("Integer.parseInt(\"1011\", 2)", Integer.parseInt("1011", 2));
row("Integer.toHexString(255)", Integer.toHexString(255));
}
} C++ · compiled/bases.cpp
// Binary text in C++: signed digits, a 32-bit pattern, and the way back.
#include <bitset>
#include <cstdint>
#include <format>
#include <print>
#include <stdexcept>
#include <string>
void row(const char* label, const auto& value) {
std::println("{:<31}{}", label, value);
}
int main() {
int x = -5;
std::string bits = std::format("{:032b}", static_cast<std::uint32_t>(x)); // the 32-bit pattern of x
row("std::format(\"{:b}\", x)", std::format("{:b}", x));
row("32-bit pattern", bits);
row("std::bitset<32>(x)", std::bitset<32>(x).to_string());
try {
row("std::stoi(bits, nullptr, 2)", std::stoi(bits, nullptr, 2));
} catch (const std::out_of_range&) {
row("std::stoi(bits, nullptr, 2)", "std::out_of_range"); // 4294967291 does not fit an int
}
row("back to signed", static_cast<std::int32_t>(std::stoul(bits, nullptr, 2)));
row("std::stoi(\"1011\", nullptr, 2)", std::stoi("1011", nullptr, 2));
row("std::format(\"{:x}\", 255)", std::format("{:x}", 255));
} Go · compiled/bases.go
// Binary text in Go: signed digits, a 32-bit pattern, and the way back.
package main
import (
"fmt"
"strconv"
)
func row(label string, value any) {
fmt.Printf("%-33s%v\n", label, value)
}
func main() {
var x int32 = -5
bits := fmt.Sprintf("%032b", uint32(x)) // the 32-bit pattern of x
row(`fmt.Sprintf("%b", x)`, fmt.Sprintf("%b", x))
row("32-bit pattern", bits)
if _, err := strconv.ParseInt(bits, 2, 32); err != nil {
row("strconv.ParseInt(bits, 2, 32)", err) // 4294967291 does not fit 32 signed bits
}
u, _ := strconv.ParseUint(bits, 2, 32)
row("back to signed", int32(u))
n, _ := strconv.ParseInt("1011", 2, 64)
row(`strconv.ParseInt("1011", 2, 64)`, n)
row("strconv.FormatInt(255, 16)", strconv.FormatInt(255, 16))
} Runs on this device, in your browser. The first run of each language downloads it: Python (about 13.5 MB) or JavaScript (about 0.6 MB), which is kept for the next runs.
Your run, in this browser
from session import run, versions
versions("java -version", "clang++ --version", "go version")
run("""
java Bases.java
clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined bases.cpp -o bases
./bases
go run bases.go
""") Output
# openjdk version "25.0.4.1" LTS
# Apple clang version 21.0.0 (clang-2100.3.34.2)
# go version go1.27.1 darwin/arm64
$ java Bases.java
Integer.toString(x, 2) -101
Integer.toBinaryString(x) 11111111111111111111111111111011
Integer.parseInt(bits, 2) NumberFormatException
Integer.parseUnsignedInt(bits, 2) -5
Integer.parseInt("1011", 2) 11
Integer.toHexString(255) ff
$ clang++ -std=c++23 -Wall -Wextra -fsanitize=address,undefined bases.cpp -o bases
$ ./bases
std::format("{:b}", x) -101
32-bit pattern 11111111111111111111111111111011
std::bitset<32>(x) 11111111111111111111111111111011
std::stoi(bits, nullptr, 2) std::out_of_range
back to signed -5
std::stoi("1011", nullptr, 2) 11
std::format("{:x}", 255) ff
$ go run bases.go
fmt.Sprintf("%b", x) -101
32-bit pattern 11111111111111111111111111111011
strconv.ParseInt(bits, 2, 32) strconv.ParseInt: parsing "11111111111111111111111111111011": value out of range
back to signed -5
strconv.ParseInt("1011", 2, 64) 11
strconv.FormatInt(255, 16) ff
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 bases_session.py
- Signed text. Python’s
bin()andformat(x, "b"), JavaScript’stoString(2), Java’sInteger.toString(x, 2), C++’sstd::format("{:b}", x)and Go’s%ball write a minus sign and the magnitude:-101. Onlybin()adds the0bprefix. - The stored bits. Convert to an unsigned 32-bit value first and pad to 32 digits:
format(x & 0xFFFFFFFF, "032b"),(x >>> 0).toString(2).padStart(32, "0"),std::format("{:032b}", static_cast<std::uint32_t>(x))orfmt.Sprintf("%032b", uint32(x)). Java’sInteger.toBinaryStringalready writes the unsigned bits, but without leading zeros, so pad its result when bit 31 is off. C++’sstd::bitset<32>(x).to_string()gives all 32 digits as well. - Parsing a pattern with bit 31 on. Python’s
int(s, 2)and JavaScript’sparseInt(s, 2)read11111111111111111111111111111011as 4294967291. Java’sInteger.parseInt, C++’sstd::stoiand Go’sstrconv.ParseIntwith 32 bits refuse it, because 4294967291 does not fit a signed 32-bit integer. Parse it as unsigned instead (Integer.parseUnsignedInt,std::stoulfollowed by a cast tostd::int32_t,strconv.ParseUintfollowed byint32()), or apply the signed conversion yourself:| 0in JavaScript, and in Python the same subtraction asto_int32.
Other bases work the same way. Pass 16 or 8 instead of 2 (int("ff", 16), parseInt("ff", 16),
Integer.parseInt("ff", 16), strconv.ParseInt("ff", 16, 64)), and for output use hex(), toString(16),
Integer.toHexString, std::format("{:x}", n) or strconv.FormatInt(n, 16). Python’s int(s, 0) and
JavaScript’s Number(s) also accept a literal’s own prefix, as in 0b1011 or 0xff.
Interview questions
How do you test, set, clear and toggle bit i of an integer x? (warm-up)
Build the mask 1 << i, which has only bit i on, and combine it with x. To test, compute x & (1 << i): it is
not 0 exactly when the bit is on, so compare it with != 0, never with > 0, because for bit 31 of a signed 32-bit
value the result is negative; (x >> i) & 1 gives the bit as 0 or 1. To set, use x | (1 << i); to clear,
x & ~(1 << i), which ANDs with a mask that has every bit on except bit i (Go also has x &^ (1 << i)); to
toggle, x ^ (1 << i). When x has 64 bits, build the mask from a 64-bit one, such as 1L << i in Java or
1ULL << i in C++, because a 32-bit 1 << 40 never reaches bit 40.
What is the difference between >> and >>> in Java and JavaScript? (warm-up)
>> is an arithmetic shift: it copies the sign bit into the places it frees on the left, so a negative number
stays negative, and x >> k equals x divided by 2ᵏ, rounded down: -7 >> 1 is −4. >>> is a logical shift: it
fills those places with zeros, so for any shift of at least one place the result is not negative: -7 >>> 1 is
2147483644 for a 32-bit value. In JavaScript >>> also converts its operand to an unsigned 32-bit number, so
x >>> 0 reads a value’s bits as unsigned. Python, C++ and Go have no >>>: Python masks before shifting, and C++
and Go shift an unsigned type instead.
Key takeaways
x & (1 << i)tests biti,x | (1 << i)sets it,x & ~(1 << i)clears it andx ^ (1 << i)toggles it, in all five languages; Go writes NOT as^and clears with&^.- Test bits with
!= 0, never> 0: in a signed 32-bit value, bit 31 makes the result negative. - Negative numbers are two’s complement everywhere. Python behaves as if it had infinitely many sign bits, so mask
with
& 0xFFFFFFFFto see 32 of them and subtract 2³² to read them as signed. - Right shifts of negative numbers round down; division by 2 rounds towards zero, except Python’s
//. - Shifting a 32-bit value by 32 gives 1 in JavaScript and Java, 0 in Go and undefined behaviour in C++. Keep shift counts below the width, and build wide masks from a 64-bit or unsigned 1.
- Parenthesise bit expressions next to comparisons and arithmetic:
(x & 1) == 0,1 << (i + 1).
Exercise
Exercise · Easy · Python
Apply set, clear and toggle steps to a 32-bit flag mask
Write flags_after(ops). A profile keeps its settings in one 32-bit mask that starts at 0, with every flag off. ops lists the steps to apply, in order, as pairs (action, bit) with a bit from 0 to 31: "set" turns that bit on, "clear" turns it off and "toggle" flips it. Return the final mask as a signed 32-bit integer, the value a Java int or a JavaScript bitwise result would hold, so a mask with bit 31 on is negative. Raise ValueError for any other action.
flags_after([("set", 0), ("set", 2), ("toggle", 2), ("set", 5)]) # 33: bits 0 and 5 are on
flags_after([("set", 31)]) # -2147483648
flags_after([("set", 31), ("set", 0), ("toggle", 1)]) # -2147483645
flags_after([]) # 0
Clearing a bit that is already off changes nothing, and so does setting a bit that is already on.
Starter code · flags_after.py
def flags_after(ops):
"""The flag mask after applying ops to a mask that starts at 0, as a signed 32-bit integer.
ops is a list of (action, bit) pairs: action is "set", "clear" or "toggle", and bit is 0 to 31.
"""
# Replace this line with your code.
return 0 The sample tests · test_flags_after.py
import random
from flags_after import flags_after
def test_examples():
"""the examples of the prompt"""
assert flags_after([("set", 0), ("set", 2), ("toggle", 2), ("set", 5)]) == 33
assert flags_after([("set", 31)]) == -2147483648
assert flags_after([("set", 31), ("set", 0), ("toggle", 1)]) == -2147483645
assert flags_after([]) == 0
def test_repeated_steps():
"""clearing a bit that is off and setting a bit that is on change nothing"""
assert flags_after([("clear", 7)]) == 0
assert flags_after([("set", 4), ("set", 4)]) == 16
assert flags_after([("set", 4), ("clear", 4), ("clear", 4)]) == 0
def test_toggle_twice():
"""two toggles of the same bit cancel out"""
assert flags_after([("toggle", 9), ("toggle", 9)]) == 0
assert flags_after([("set", 3), ("toggle", 31), ("toggle", 31)]) == 8
def test_bit_31():
"""bit 31 is the sign bit of a signed 32-bit integer"""
assert flags_after([("toggle", 31)]) == -2147483648
assert flags_after([("set", 31), ("clear", 31), ("set", 30)]) == 1073741824
assert flags_after([("set", b) for b in range(32)]) == -1
assert flags_after([("set", b) for b in range(31)]) == 2147483647
def test_against_a_list_of_bits():
"""300 random lists of steps, compared with a slow check that keeps 32 booleans"""
rng = random.Random(31)
for _ in range(300):
ops = [(rng.choice(["set", "clear", "toggle"]), rng.choice([0, 1, 15, 30, 31, rng.randrange(32)])) for _ in range(rng.randint(0, 25))]
on = [False] * 32
for action, bit in ops:
if action == "set":
on[bit] = True
elif action == "clear":
on[bit] = False
else:
on[bit] = not on[bit]
expected = sum(1 << b for b in range(31) if on[b]) - (1 << 31 if on[31] else 0)
assert flags_after(ops) == expected, f"flags_after({ops!r})"
def test_unknown_action():
"""an action other than set, clear or toggle raises ValueError"""
try:
flags_after([("flip", 3)])
except ValueError:
return
raise AssertionError("expected ValueError for the action 'flip'") A hint
Keep the mask as an ordinary Python integer while you apply the steps: mask |= 1 << bit sets a bit, mask &= ~(1 << bit) clears it and mask ^= 1 << bit toggles it. Only at the end turn the mask into a signed 32-bit value: keep its low 32 bits with & 0xFFFFFFFF, and if bit 31 of that number is 1, the signed value is 2³² less.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.
Check yourself
6 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.
References
- Bitwise operations on integer types (The Python Standard Library: built-in types) (Python Software Foundation)
- Additional methods on integer types: int.bit_length() and int.bit_count() (Python Software Foundation)
- Operator precedence (The Python Language Reference) (Python Software Foundation)
- bin() and int() (The Python Standard Library: built-in functions) (Python Software Foundation)
- ECMAScript language specification: Number::leftShift, signedRightShift and unsignedRightShift (Ecma International (TC39))
- ECMAScript language specification: ToInt32 (Ecma International (TC39))
- ECMAScript language specification: binary bitwise operators (Ecma International (TC39))
- ECMAScript language specification: BigInt::unsignedRightShift (Ecma International (TC39))
- ECMAScript language specification: Math.clz32 (Ecma International (TC39))
- The Java Language Specification, Java SE 25: 15.19 Shift operators (Oracle)
- The Java Language Specification, Java SE 25: 15.22 Bitwise and logical operators (Oracle)
- The Java Language Specification, Java SE 25: 15.15.5 Bitwise complement operator ~ (Oracle)
- Java SE 25 API: Integer (Oracle)
- Working Draft, Programming Languages: C++ (N4950): 6.8.2 [basic.fundamental], 7.6.7 [expr.shift], 22.15.7 [bit.count] (ISO/IEC JTC1/SC22/WG21)
- Fundamental types: integer types and their representation (cppreference.com)
- Arithmetic operators: bitwise shift operators (cppreference.com)
- std::popcount (cppreference.com)
- UndefinedBehaviorSanitizer (Clang documentation) (LLVM Project)
- The Go Programming Language Specification: arithmetic operators (The Go Authors)
- The Go Programming Language Specification: operator precedence (The Go Authors)
- The Go Programming Language Specification: representability (The Go Authors)
- Package bits (math/bits) (The Go Authors)
- Package strconv (The Go Authors)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress