In Go, what do the expressions -7/2 and -7%2 evaluate to, and what rule fixes the sign of the remainder?
answer
- two rules, one identity
- truncate, do not floor
- the remainder follows the dividend
- a negative index panics on lookup
basics
~10 sGo truncates integer division toward zero, so -7/2 is -3, and the remainder takes the sign of the dividend, so -7%2 is -1. The identity x == (x/y)*y + x%y always holds.
solid answer
~40 sInteger division in Go truncates toward zero rather than toward negative infinity, so `-7/2` is `-3`, not `-4`. The remainder is then defined by the identity `x == (x/y)*y + x%y`, which forces `%` to carry the sign of the **dividend**: `-7%2` is `-1`, and `7%-2` is `+1`. The spec also guarantees `|x%y| < |y|`. That is the opposite of Python's floored `%`, and it is the reason a sharding expression such as `int(hash) % numShards` can return a negative index and panic on the slice lookup when the converted hash happens to be negative. The fix is to do the modulo in unsigned arithmetic — `int(h % uint64(numShards))` — or to normalise with `((x % n) + n) % n`. Go's standard library has no integer floored-modulo helper, so you write that yourself.
code
go · 5 linesx, y := -7, 2
fmt.Println(x/y, x%y) // -3 -1
p, q := 7, -2
fmt.Println(p/q, p%q) // -3 1go deeper
Be ready to state both rules from memory and evaluate the four sign combinations on the spot: -7/2 is -3 and -7%2 is -1. Recite the identity x == (x/y)*y + x%y as the reason.
Explain why the remainder's sign follows from the truncating quotient rather than being a separate rule, and contrast Go's truncated division with the floored convention other languages use.
Show that you spot the failure in review: a signed value feeding % into an index expression. Name the unsigned fix and explain why the resulting bug is data-dependent and therefore intermittent.
Own the guidance: index-producing arithmetic should be unsigned by construction, not corrected afterwards, and a helper that a whole codebase uses beats twenty hand-written normalisations.
## The two rules Go's specification defines integer `/` and `%` together, not separately: - The quotient `q = x / y` is **truncated toward zero** — the fractional part is discarded, so the result moves toward 0 regardless of sign. - The remainder `r = x % y` is whatever makes `x == q*y + r` true, with `|r| < |y|`. Because the remainder is derived from a quotient that truncates toward zero, `%` always ends up with the **sign of the dividend** (the left operand), or is zero. The divisor's sign never shows up in the remainder. | expression | value | why | |---|---|---| | `7 / 2` | `3` | 3.5 truncated toward zero | | `-7 / 2` | `-3` | -3.5 truncated toward zero, not floored to -4 | | `7 / -2` | `-3` | same magnitude, negative sign | | `-7 % 2` | `-1` | -7 = (-3)*2 + (-1) | | `7 % -2` | `1` | 7 = (-3)*(-2) + 1 | The useful mental shortcut: **`/` cuts toward zero; `%` copies the dividend's sign.** ## Truncated versus floored The other common convention, used by Python and by mathematical modular arithmetic, is *floored* division: the quotient rounds toward negative infinity and the remainder takes the **divisor's** sign, so `-7 // 2` is `-4` and `-7 % 2` is `1`. Go, like C and Java, chose truncation, mainly because it maps directly onto what CPUs implement. Neither convention is more correct; the bug comes from assuming the one you learned elsewhere. One consequence worth knowing: `x % y` in Go is *not* a mathematical "mod n" that maps every input into `[0, n)`. It maps into `(-|y|, |y|)`. ## Where this actually bites: a shard index The classic production failure is a router that maps a key onto one of N shards: ```go h := fnv.New64a() h.Write([]byte(key)) shard := int(h.Sum64()) % len(shards) // BUG return shards[shard] ``` `h.Sum64()` returns a `uint64`. Converting it to `int` on a 64-bit platform reinterprets the top bit as a sign bit, so roughly half of all keys produce a **negative** `int`. Feed a negative dividend to `%` and you get a negative remainder, and `shards[-3]` panics with an index-out-of-range runtime error. What makes this miserable is that it is data-dependent: it depends on which keys the test happens to generate, so it shows up as a build that fails maybe one run in five, and the only evidence is a panic's stack trace pointing at the slice index rather than at the `%`. Three correct fixes: 1. **Stay unsigned.** `shard := int(h.Sum64() % uint64(len(shards)))`. An unsigned remainder can never be negative, and the result is smaller than `len(shards)`, so the conversion to `int` is safe. 2. **Normalise.** `shard := ((x % n) + n) % n` turns a truncated remainder into a floored one for positive `n`. 3. **Mask, when N is a power of two.** `shard := int(h.Sum64() & uint64(n-1))` avoids the division entirely. Option 1 is the one to reach for: it removes the signed value from the expression instead of repairing it afterwards. ## Details that come up in follow-ups - **Untyped constants follow the same rule.** `-7/2` written with literals is folded at compile time, still truncating toward zero, still `-3`. - **`%` is integers only.** Go does not define the `%` operator on floating-point operands at all; attempting it is a compile-time error. - **The one arithmetic special case.** If `x` is the most negative value of its signed type, `x / -1` equals `x` and `x % -1` is `0`, because the mathematically correct quotient has no representation in the type. - **Both operands must have the same type.** Go has no implicit numeric conversion, so `someInt % someInt64` does not compile; you convert explicitly, and it is exactly that explicit `int(...)` conversion that introduces the sign bug above. ## How to talk about it in an interview State the two rules (truncate toward zero, remainder follows the dividend), give `-7/2 == -3` and `-7%2 == -1` as evidence, then name a place where the sign matters — index computation, bucketing, round-robin, wrapping a clock value. The people who get caught by this are not the ones who cannot recite the rule; they are the ones who never considered that the dividend could be negative.
- How would you map a possibly negative int onto the range 0 to n-1?Either normalise the truncated remainder with `((x % n) + n) % n`, or avoid the signed value entirely by converting to an unsigned type first and taking `x % uint64(n)`. When `n` is a power of two, `x & uint64(n-1)` does the same job without a division. The unsigned route is preferable because it removes the sign rather than patching it after the fact.
- Why does converting a uint64 hash to int sometimes produce a negative number?On a 64-bit platform `int` is 64 bits and signed, so the conversion keeps all 64 bits and reinterprets the top one as the sign bit. Any hash with the high bit set becomes negative. The conversion never fails or reports anything — it is a pure reinterpretation — which is why the bug surfaces far away, at the slice index.
- Does Go's % operator work on float64?No. Go defines `%` only for integer operands; writing it with floating-point operands is a compile-time error rather than a run-time surprise. That is a deliberate difference from languages that overload `%` for both, and it forces you to reach for a floating-point-specific helper when you really want one.
Truncation toward zero is like deleting the digits after the decimal point rather than rounding down: -3.5 becomes -3, not -4. The remainder is then whatever is left over on the same side of zero you started from.
saying these in an interview costs you the question
- Says Go floors like Python, so -7/2 is -4
- Claims an integer remainder is always non-negative
- Thinks % takes the sign of the divisor
- Uses % on a converted signed hash for a shard index without guarding the sign
- Believes % also works on float64 operands