How a Chip Computes Sine With Only Shifts and Adds
The whole reason to hang four kilograms of lead down a stairwell is one equation: the swing plane drifts by 15°·sin(latitude) each hour. At my latitude that should be about 11.7°/hr, a full turn in roughly a day and a bit. To predict it, I need sin(51.05°). Easy — Math.sin and done. But it nagged at me that the pocket calculators and navigation computers of the 1980s, the ones with no floating-point multiplier to spare, had to earn that sine somehow.
They used CORDIC. Jack Volder cooked it up in 1959 for the B-58 bomber’s navigation computer, and it hit its stride two decades later inside HP calculators and early DSP chips, where a hardware multiplier was an extravagance and a barrel shifter was cheap. The trick: rotate a vector toward your target angle in a fixed set of ever-shrinking steps, each step being a rotation by arctan(2⁻ⁱ). Because those magic angles are powers of two, every rotation is just a bit-shift and an add. Seed the vector with a precomputed gain constant and the final coordinates are the cosine and sine.
const K = 0.6072529350088812561;
function cordic(theta: number, n = 16): [number, number] {
let x = K, y = 0, z = theta;
for (let i = 0; i < n; i++) {
const d = z < 0 ? -1 : 1, f = 2 ** -i;
const nx = x - d * y * f;
y = y + d * x * f;
x = nx;
z = z - d * Math.atan(f);
}
return [x, y]; // [cos θ, sin θ]
}
const lat = 51.05 * Math.PI / 180; // Calgary
const [, sinLat] = cordic(lat);
console.log((15 * sinLat).toFixed(3) + " deg/hr precession");
11.665 deg/hr precession
The Math.atan(f) calls are cheating — a real CORDIC unit reads those from a tiny lookup table burned into ROM, sixteen constants and no transcendental functions anywhere on the die. Same shape in OCaml:
let k = 0.6072529350088812561
let cordic theta =
let x = ref k and y = ref 0. and z = ref theta in
for i = 0 to 15 do
let f = 2. ** float_of_int (- i) in
let d = if !z < 0. then -1. else 1. in
let nx = !x -. d *. !y *. f in
y := !y +. d *. !x *. f;
x := nx;
z := !z -. d *. atan f
done;
(!x, !y) (* cos θ, sin θ *)
let () =
let lat = 51.05 *. Float.pi /. 180. in
let _, s = cordic lat in
Printf.printf "%.3f deg/hr\n" (15. *. s)
Both print 11.665. Sixteen iterations buys about four good decimal digits — add more steps, add more digits, one bit at a time. There’s a pleasing symmetry I didn’t expect: the pendulum measures my latitude by slowly rotating a plane through a sequence of small angular increments, and CORDIC recovers the sine of that latitude by rotating a vector through a sequence of small angular increments. One does it with a lead weight and the Earth; the other does it with a shift register. I still haven’t gotten a clean latitude reading off the actual pendulum — but at least the arithmetic that’s supposed to check my work owes nothing to a multiplier.