build-a-protobuf-decoder / lesson-11.md
Lesson 11 · Scalar values

Zigzag for signed integers

Because a plain negative int wastes ten bytes, protobuf offers sint32 and sint64 that zigzag small negatives into small unsigned varints. Today you build the zigzag mapping and its inverse so signed values encode compactly.

The goal

Map signed integers to and from their zigzag varint encoding.

Start here - the target
TO DO
Scenario: Zigzag interleaves negatives and positives
Giventhe signed values 0, -1, 1, -2, 2
Wheneach is zigzag-encoded
Thenthey map to 0, 1, 2, 3, 4 respectively
Anddecoding zigzag runs the mapping backward, so 1 decodes to -1 and 3 decodes to -2, round-tripping every value
Background

Zigzag encoding makes small-magnitude negatives cheap by interleaving signs: 0 maps to 0, -1 to 1, 1 to 2, -2 to 3, 2 to 4, so a value near zero stays near zero whether it is positive or negative, and the resulting small unsigned number varint encodes to one byte. The sint32 and sint64 types use this; plain int32 and int64 from the last lesson do not.

The encode is (v << 1) ^ (v >> 63): shifting left by one makes room for the sign in bit 0, and the arithmetic right shift by 63 produces all-ones for a negative and all-zeros for a non-negative, which the XOR uses to flip the other bits for negatives. Decoding reverses it: (u >> 1) ^ -(u & 1) moves the magnitude back down and rebuilds the sign from bit 0. Confirm the two are inverses on the whole sample - a codec that cannot round-trip its own signed values is worse than useless.

Make it work
// encode: fold the sign bit into bit 0 (arithmetic shift copies the sign)
func zigzagEncode(v int64) uint64 { return uint64((v << 1) ^ (v >> 63)) }
// decode: pull bit 0 back out as the sign
func zigzagDecode(u uint64) int64 { return int64(u>>1) ^ -int64(u&1) }
CheckpointDONE
You can zigzag-encode and decode signed integers. Commit and stop here.