skip to content

questions

12

Why does an 8-bit two's-complement integer range from -128 to 127 rather than -127 to 127?

level: juniorimportance: must knowfreq 75%

answer

  1. count the total bit patterns first
  2. where does zero live, and how many zeros are there?
  3. the top bit carries a negative weight, not a flag
  4. split: top bit set vs clear, 128 each
  5. try ~n + 1 on the lowest value

basics

~20 s

Two's complement gives the top bit a weight of -128, so the 256 patterns split into 128 negatives (-128 to -1) and 128 non-negatives. Zero uses one non-negative pattern, leaving only 127 positives — so -128 has no positive counterpart.

solid answer

~40 s

Eight bits give exactly 256 patterns. Two's complement assigns the most significant bit a weight of -128 and the rest their usual positive weights, so every pattern with the top bit set is negative — that's 128 values, -128 through -1 — and every pattern with it clear is non-negative — 128 values, 0 through 127. Zero occupies one slot on the non-negative side, which is what makes the range asymmetric. Negation is `~n + 1`: flip every bit, add one. Apply it to -128 (`10000000`): flipping gives `01111111`, adding one gives `10000000` — the same pattern back, because +128 would need a ninth bit. Unlike sign-magnitude or one's complement, two's complement has a single zero and lets one adder circuit serve signed and unsigned arithmetic alike, which is why it won.

go deeper

for a junior

Be ready to state the n-bit range, explain that the top bit weighs -2^(n-1), and negate a small number by flipping bits and adding one on the whiteboard without hesitation.

for a middle

An interviewer expects the full mechanism: why zero's placement creates the asymmetry, why ~n + 1 works algebraically, and what pattern the minimum value's negation lands on and why.

for a senior

Connect representation to consequences: recognize when a computation can reach the unpairable minimum value, and explain why sanity checks on widths and ranges belong at the boundaries where binary data enters the system.

for a principal

Own the framing when this bites a design: fixed-width integer semantics are a contract across languages, wire formats, and hardware, and you should be able to justify width and signedness choices in a data format review from these first principles.

## Start by counting patterns An 8-bit value has exactly 2^8 = 256 distinct bit patterns — that budget is fixed before any interpretation is chosen. Read as **unsigned**, the patterns cover 0..255 with the usual place values: bit *i* is worth 2^i. A **signed** encoding must spend the same 256 patterns to cover both signs somehow. ## What two's complement actually is Two's complement is not "a sign flag plus a magnitude". It is a weighting scheme: in an n-bit number, the most significant bit carries weight **-2^(n-1)** and every other bit keeps its positive weight. For 8 bits: ``` value = -128*b7 + 64*b6 + 32*b5 + 16*b4 + 8*b3 + 4*b2 + 2*b1 + 1*b0 ``` So `11111011` is -128 + 64 + 32 + 16 + 8 + 2 + 1 = **-5**, and `10000000` is exactly **-128**. From this one rule everything follows: - Every pattern with the top bit **set** is negative — 128 patterns covering -128..-1. - Every pattern with the top bit **clear** is non-negative — 128 patterns covering 0..127. - Zero (`00000000`) is unique and sits on the non-negative side, which is why there are 128 negatives but only 127 positives. The asymmetry is arithmetic, not a design accident. ## Negation: ~n + 1, and why it works For any n, `n + ~n` sets every bit (each bit position has exactly one 1 between them), and the all-ones pattern is -1. So `~n = -n - 1`, and therefore `~n + 1 = -n`. Walkthrough for 5: ``` 5 = 00000101 ~5 = 11111010 (this is -6) ~5 + 1 = 11111011 = -5 ``` Now apply it to the minimum value: ``` -128 = 10000000 ~(-128) = 01111111 (= 127) +1 = 10000000 (the pattern for -128 again) ``` The negation lands on the same pattern, because the true answer, +128, does not fit in 8 bits — it would need 9. This is the **representational** statement of the famous edge case: the minimum signed value simply has no positive counterpart at the same width. (What a running program does when it evaluates that negation — wrapping, trapping — is a separate overflow topic.) ## Why this encoding won Two other historical encodings spent the 256 patterns differently. **Sign-magnitude** uses the top bit as a pure flag over a 7-bit magnitude, giving -127..+127 — but with two zeros (+0 and -0) and hardware that must special-case subtraction. **One's complement** negates by flipping all bits, also yielding two zeros and an awkward "end-around carry" in addition. Two's complement has a **single zero**, and — decisively — the **same binary adder** produces correct results whether the operands are read as signed or unsigned, because the encoding is arithmetic modulo 2^n. Subtraction becomes "add the negation". That hardware economy is why essentially every mainstream platform committed to it: C++20 made two's complement the only permitted representation after decades of theoretical latitude, and Java, Go, and Rust define their fixed-width integer types as two's complement outright. ## Generalizing the range An n-bit two's-complement integer spans **-2^(n-1) .. 2^(n-1) - 1**: | Width | Minimum | Maximum | |-------|---------|---------| | 8-bit | -128 | 127 | | 16-bit | -32,768 | 32,767 | | 32-bit | -2,147,483,648 | 2,147,483,647 | | 64-bit | -2^63 | 2^63 - 1 | At every width the same facts hold: one more negative than positives, a unique zero, and a minimum value whose negation is not representable. Interviewers probe this because every bit trick, mask, and overflow question downstream assumes you can read a pattern both ways and negate by `~n + 1` without hesitation.

  • Walk me through why ~n + 1 negates a number.
    For any pattern n, `n + ~n` has a 1 in every position, and the all-ones pattern equals -1 in two's complement. So `~n = -n - 1`, and adding 1 gives `~n + 1 = -n`. Concretely: 5 is `00000101`, flipping gives `11111010` (-6), adding one gives `11111011`, which is -128 + 123 = -5.
  • What does ~n + 1 produce when n is already the minimum value?
    The same pattern back. `10000000` flips to `01111111` (127), and adding one returns `10000000`. The mathematically correct answer, +2^(n-1), needs one more bit than the width provides, so no correct representation exists at that width — the negation has no answer, not a wrong answer.
  • Why did two's complement beat sign-magnitude and one's complement in hardware?
    It has a single zero, while both rivals waste a pattern on -0 and force equality checks to treat two patterns as equal. More decisively, two's complement is arithmetic modulo 2^n, so one plain binary adder computes correct signed and unsigned sums with no special cases, and subtraction reduces to adding the negation.

Think of a clock face: 11 o'clock can equally be read as "one hour before 12". Two's complement reads the upper half of the number circle as negative distances back from the rollover point instead of large positive values.

saying these in an interview costs you the question

  • Describes the top bit as a pure sign flag with the other bits unchanged
  • Claims the range is symmetric, or that there are two zeros
  • Cannot execute ~n + 1 on a concrete pattern
  • Thinks the missing +128 is a compiler or language quirk rather than a property of the encoding
  • Says the asymmetry exists because hardware reserves a pattern for errors

context

open as a page

How do you clear bit k of a status word with a mask, leaving every other bit untouched?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Use n = n & ~(1 << k). The shifted mask holds a single 1 at position k; inverting it gives all ones except there, so the AND forces that bit to 0 and leaves the rest unchanged.

open as a page

Why does extracting a 12-bit length field from a packed 32-bit header need both a right shift and a mask?

level: juniorimportance: must knowfreq 68%

basics

~20 s

The shift moves the field down to bit 0 so it reads as a number; the mask clears the neighbouring fields still sitting above it. Shift alone leaves garbage on top, mask alone leaves the value scaled.

open as a page

What does n = n & (n - 1) do each pass of a bit-counting loop, and how many passes run?

level: middleimportance: must knowfreq 70%

basics

~20 s

Each pass clears the lowest set bit of n and leaves every other bit alone, so the loop runs exactly once per set bit — the population count — not once per bit of the word width.

open as a page

Why does an arithmetic right shift of a negative pixel delta not match integer division by two?

level: middleimportance: must knowfreq 58%

basics

~20 s

An arithmetic right shift rounds toward negative infinity; truncating integer division rounds toward zero. They agree on non-negative values and when the division is exact, but for -7 the shift gives -4 and the division gives -3.

open as a page

Why does the single byte 0xC8 decode as 200 when read as unsigned but as -56 when read as signed two's complement?

level: middleimportance: should knowfreq 40%

basics

~20 s

The bits never change — the interpretation does. Unsigned reading weights the top bit +128, so 11001000 is 200. Two's complement weights the same bit -128, giving -128 + 64 + 8 = -56. Signedness is a property of the reading, not the byte.

open as a page

What is sign extension, and what goes wrong when a negative 16-bit value is widened to 32 bits by zero-filling?

level: middleimportance: should knowfreq 45%

basics

~20 s

Sign extension copies the sign bit into every new high-order bit when widening, which preserves the two's-complement value. Zero-filling instead reinterprets a negative as a large positive: the 16-bit value -200 arrives in 32 bits as 65336.

open as a page

A capacity validator accepts n when (n & (n - 1)) == 0 — which inputs wrongly slip through?

level: middleimportance: should knowfreq 55%

basics

~20 s

Zero slips through, and on a signed word so does the most-negative value. The expression only asserts "at most one bit is set", so the real power-of-two test needs the guard n > 0 in front of it.

open as a page

What does flags & ACK_MASK == ACK_MASK test when & binds more loosely than ==?

level: middleimportance: should knowfreq 45%

basics

~20 s

It tests the lowest bit of flags, not the ACK bit. The comparison binds tighter, runs first and yields 1, so the line reduces to flags AND 1 — a silent bug that still compiles.

open as a page

Why does the mask expression (1 << bits) - 1 break when bits equals the integer's full width?

level: seniorimportance: should knowfreq 38%

basics

~20 s

A shift count equal to the operand's width is outside what shifts define: languages variously wrap the count, yield zero, trap, or leave it undefined. Build the all-ones mask without shifting by the full width.

open as a page

Why can a backwards array scan using an unsigned index never terminate when its loop guard is `i >= 0`?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

An unsigned integer cannot hold a negative value, so the guard i >= 0 is always true. When i reaches 0, decrementing wraps it to the maximum unsigned value — the bit pattern of -1 — and the scan runs past the array forever.

open as a page

A grid engine counts set bits in a 64-bit occupancy word a billion times a frame — is the clear-lowest-bit loop good enough?

level: seniorimportance: nice to knowfreq 30%

basics

~20 s

No. The loop costs one unpredictable branch per set bit, so its price rises with board density. At that call volume use a fixed-cost population-count instruction, with a branch-free shift-and-mask fallback where the hardware lacks one.

open as a page