build-a-bignum-library / lesson-30.md
Lesson 30 · Higher operations and the capstone

Rendering to hexadecimal

Decimal is not the only base worth reading. Hexadecimal output is just repeated division by sixteen, collecting remainders - a direct use of the short division you built. Today you add Hex.

The goal

Render a BigInt as a lowercase hexadecimal string via repeated division by sixteen.

Start here - the target
TO DO
Scenario: Hex output collects base-sixteen remainders
Giventhe BigInt 255
WhenHex is evaluated
Thenthe result is "ff"
AndHex(4096) is "1000", Hex(18446744073709551616) is "10000000000000000", Hex(0) is "0", and Hex(-255) is "-ff"
Background

Converting to another base is repeated division by that base, reading the remainders off from least significant to most. For hex, divide the magnitude by 16 over and over; each remainder is a value from 0 to 15, one hexadecimal digit. Because the digits come out lowest first, you reverse the collected string at the end, then prepend a - if the number is negative and print "0" for zero.

This reuses divScalar unchanged - 16 is a perfectly ordinary single-limb divisor - which is why building division early pays off now. 255 is "ff", 4096 is "1000", and 2^64 is 1 followed by sixteen zeros, exactly the fixed-width hex you would expect but with no width limit. Parsing hex back in is the inverse, and the last new operation before the capstone.

Make it work
const hexDigits = "0123456789abcdef"
func (x BigInt) Hex() string {
if x.sign == 0 { return "0" }
m := x.mag
var out []byte
for len(m) > 0 {
var d uint32
m, d = divScalar(m, 16) // remainder is the next hex digit
out = append(out, hexDigits[d])
}
// reverse out, prepend '-' when negative
}
CheckpointDONE
BigInts render to hexadecimal. Commit and stop here.