skip to content

Why can taking the absolute value of a fixed-width signed integer return a negative number?

level: middleimportance: should knowfreq 38%

answer

  1. count the values on each side of zero
  2. one end of the range reaches further
  3. absolute value is a negation in disguise
  4. the result would need a value that does not exist
  5. so it wraps and hands back the input

basics

~20 s

Signed fixed-width ranges are asymmetric — there is one more negative value than positive. Negating the most negative value has no representable result, so it wraps back to itself, and absolute value hands you that same negative number.

solid answer

~50 s

A 32-bit signed type spans -2,147,483,648 to +2,147,483,647: the negative end reaches one further than the positive end. Absolute value of a negative input is implemented as a negation, and negating the most negative value produces a result that does not exist in the type, so it wraps and returns the input unchanged — still negative. In a distance metric this is quietly lethal: `d = absolute_value(a - b)` then `if d < threshold` accepts the largest possible discrepancy as if it were the smallest, because a hugely negative `d` passes any positive threshold. Two further points make it realistic: the difference of two in-range values can itself land on that sentinel, and the most negative value is widely used as a "missing" marker in wire formats. Guards are to compute the difference in a wider type, or to reject the sentinel explicitly before negating.

code

pseudocode · 10 lines
pseudocode
// delta, d, threshold : 32-bit signed

delta = reading_a - reading_b
d     = absolute_value(delta)     // negation wraps for the most negative value

if d < threshold then
    accept(reading_a)

// delta == -2147483648  ->  d == -2147483648
// d < threshold is TRUE, so the worst outlier is accepted

go deeper

for a junior

Recall that the signed range is lopsided — the negative end goes one further — and that the most negative value therefore has no positive twin. Knowing the magnitude of one specific input can come back negative is enough at this level.

for a middle

Explain the mechanism: absolute value negates, negation is modular, and the missing counterpart makes that input map to itself. Then show the boundary bug in a threshold check and the fix of computing the difference in a wider type.

for a senior

Show where the value comes from in practice — wrapped subtractions and sentinel-encoded fields from sources you do not control — and defend a boundary policy: decode absent markers at parse time, widen at the arithmetic, assert the postcondition.

for a principal

Own the review principle rather than the trivia: every total-looking numeric routine is interrogated at the extremes of its input type, and guards that invert on their worst case are treated as a defect class worth a shared helper and a test convention.

## The asymmetry, and what it forces A signed fixed-width type of `w` bits represents 2^w distinct values. One of them is zero. The remaining 2^w − 1 cannot split evenly into positives and negatives, and the standard encoding gives the extra one to the negative side: for 32 bits, -2,147,483,648 through +2,147,483,647. The consequence that matters at runtime is blunt — **the most negative value has no positive counterpart in its own type.** Every operation that needs one is therefore undefined in the mathematical sense and must do *something* instead. What it does, under wrapping semantics, is return the input. Negation is arithmetic modulo 2^w like everything else, and the most negative value is its own negative under that arithmetic. So absolute value — which for a negative input is exactly a negation — returns a negative number for exactly one input out of four billion. ## Why one input in four billion is worth an interview question Because the failure is not random noise, it is the *extreme* of the domain, and extremes are where guards live. Consider a sensor pipeline that filters out readings which deviate too far from a reference: compute the difference, take its magnitude, keep the reading if the magnitude is under a threshold. The one input the filter exists to reject — the maximal deviation — is the one input for which the magnitude comes back negative, and a negative number is less than any positive threshold. The filter does not merely miss the outlier; it admits it with maximum confidence. Guards that invert on their worst case are the most expensive kind. The same shape recurs wherever a magnitude feeds something that assumes non-negativity: using it as an index or an offset, using it as a size or a count, using it as a duration, sorting by it, or accumulating it. Each of those either produces nonsense or fails far away from the negation that caused it, which is what makes the bug hard to trace back. ## Two reasons it is not merely theoretical First, **the difference of two perfectly in-range values can itself fall outside the range.** Subtracting a large positive value from a large negative one is the classic case, and its wrapped result can land anywhere — including on the sentinel. So even code that validates both inputs can produce the poisoned delta. Second, **the most negative value is a popular sentinel.** Wire formats, sensor firmware and legacy schemas use it to mean "missing", "not measured" or "error". Parsing such a field into a signed type and then treating it as data walks the value straight into arithmetic. Any input source you do not control should be assumed capable of producing it. ## Handling it - **Widen before you subtract.** Compute the difference in a type at least one bit wider than the inputs. Every difference of two 32-bit values fits in 64 bits, and every 64-bit magnitude of a 32-bit-sourced difference is representable, so the whole class disappears. - **Reject the sentinel at the boundary.** If a field can legitimately carry "missing", decode it into an explicit absent state at parse time rather than letting the numeric value flow onward. - **Use checked negation where you cannot widen.** An operation that signals rather than wrapping converts the silent inversion into a caught error. - **Assert the postcondition.** A magnitude is non-negative by definition; one comparison after computing it costs nothing and names the bug precisely when it fires. What does *not* work: negating twice (both negations wrap and you are back where you started), reinterpreting the value as unsigned for display (the printed number changes, the comparison that follows does not), or assuming the input range is bounded because "real sensors do not read that" — the value can arrive from a wrapped subtraction or a sentinel, not only from a physical measurement. ## The general lesson Every total-looking function on a fixed-width type is worth interrogating at the ends of its range. Absolute value, negation, and division by −1 all fail on the same single input for the same reason. The habit that catches them is not memorising the exception list, it is asking of any numeric routine: what does this return at each extreme of its input type, and does the caller's assumption survive that answer?

  • Where does the bad magnitude actually cause damage downstream?
    Anywhere non-negativity was assumed: a threshold comparison passes for the worst possible deviation, a value used as an index or size goes out of bounds, a duration turns into a time in the past, and a sort by magnitude puts the largest outlier first. The damage typically surfaces far from the negation, which is why it is hard to trace.
  • Can the difference of two valid in-range values really produce that sentinel?
    Yes. Subtraction has the same modular semantics as addition, so a large positive minus a large negative overflows and wraps, and the wrapped result can be any value in the range including the most negative one. Validating both inputs is not enough; the operation between them is what needs the wider type.
  • Is there any input for which division also misbehaves on that value?
    Dividing the most negative value by -1 fails for the identical reason — the mathematical quotient is one past the positive ceiling. Depending on the model it wraps back to the same negative value or faults outright, and it is one of the few integer divisions that can fail with a non-zero divisor.

saying these in an interview costs you the question

  • Says absolute value is non-negative by definition, always
  • Assumes the extreme sentinel never appears in real data
  • Thinks a difference of in-range values is always in range
  • Suggests negating twice or reinterpreting as unsigned as a fix
  • Treats it as a curiosity rather than a guard-inverting bug

context