the same sight worked three ways — the navigator's log tables,
conventional floating-point software, and the machine-native integer core
| PAPER LOG TABLES | TYPICAL FLOATING C | ASTRO-NAV-INT | |
|---|---|---|---|
| INPUT | angles in degree–minute columns; the navigator supplies the decimal-point convention | double degrees, e.g. 25.238333… — nearest binary fraction, not the reading itself |
scaled integers, e.g. 1514300 milli-arcminutes — an exact encoding of the written reading; no decimal parser exists |
| REPRESENTATION | 5–6 digit log mantissas with an implied scale | IEEE 754: sign · significand · 2exp — the scale moves value to value | Q16.48, Q2.30, marcmin — the denominator is part of the design and never moves |
| NON-LINEAR MATH | lookup + interpolate in books computed before the voyage; then column addition | sin/cos/atan2/sqrt from the FPU and libm at runtime |
48-iteration integer CORDIC, integer sqrt and divide, 128-bit-widened products |
| OUTPUT | inverse table lookup restores the angle; ABC tables give azimuth | printf rounds the double back to a friendly decimal |
integer quotient/remainder printed with punctuation: 2901 → “29.01” |
| WHERE TRIG LIVES | in the printed table — evaluated years earlier, on land | in the platform's math library — whichever one is installed | in integer ladders + a 48-entry angle table the library builds once, at init, by integer series |
The classic haversine reduction: hav ZD = hav(lat − dec) + cos lat · cos dec · hav LHA, then Hc = 90° − ZD. The multiplication is the expensive part — so the navigator never multiplies. The log table turns the product into a sum of five-digit integers: look up each logarithm, add the mantissas in columns, take the antilog. The decimal point lives in the navigator's head; the paper only ever carries integers.
| QUANTITY | TABLE ENTRY | 5-DIGIT MANTISSA |
|---|---|---|
| log hav 14° | 8.17179 | 17179 (+8) |
| log cos 40° | 9.88425 | 88425 (+9) |
| log cos 20° | 9.97299 | 97299 (+9) |
| column sum | 28.02903 − 30 = −1.97097 = 8.02903 in bar notation | = integer addition |
| antilog 8.02903 | → 0.010691 | |
| + hav 20° (natural table) | + 0.030154 → hav ZD = 0.040845 | |
| ZD from hav table | 23°19.2′ → Hc = 66°40.8′ = 66.68° | |
Recomputed here to five places with the same rules a navigator followed (not scanned from a historical volume). Azimuth went the same way — time-azimuth tables or ABC tables: more lookups, more integer addition. Table precision, typically 0.1′, was a declared quantization step: everyone knew exactly how coarse the tools were.
A calculator or computer evaluates sin Hc = sin φ sin δ + cos φ cos δ cos LHA directly in double precision: Hc = 66.680325…°, with ~16 significant digits of arithmetic headroom. Precision is not the issue — a double is superb at this. Two things are quietly different from the tables, though. First, the scale stops being shared: whole degrees like 40°, 20°, 346° happen to be exactly representable, but rounding begins the moment they are converted to radians, and a typical fractional reading like 25°14.3′ is already the nearest binary fraction, not the value itself (page 2 makes this concrete). Second, the last bits are not fixed by the C/libm contract: libm is a library choice, and two correct math libraries may legitimately round a sine differently, so identical inputs can differ in the final bit across platforms. Invisible for navigation accuracy — but identical results are not guaranteed unless an application deliberately engineers and pins them.
The shipped binary, same sight (angles as integer centidegrees, 100 = 1°; the trailing argument is a corrected altitude Ho in integer milli-arcminutes, from which the intercept follows):
$ ./sight_reduction --reduce 4000 -7400 6000 2000 4012800 Hc(A): 66.68 deg Hc(B): 66.68 deg Hc(C): 66.68 deg (machine sin_hc=986027972/2^30) Zn(A): 144.95 deg true Zn(B): 144.96 deg true square-key=27022/65536 Zn(C): 144.96 deg true square-key=27022/65536 A/B altitude difference: 0.000' Intercept: 12.0 nm TOWARD
Same Hc as the log tables, to their own precision — and the machine's trick is the same kind of trick. The log table replaced the human's multiplication with table lookup and integer addition. CORDIC replaces the machine's trigonometry with table lookup and integer shift-and-add: rotate a vector through a fixed ladder of angles arctan(2−i), and each step uses one angle-table entry, two shifts, and additions or subtractions — basic operations available even on small integer CPUs. Watch the ladder home in on this very sight's azimuth:
The native-double reference azimuth of this sight is about 144.952°. In the transcript above, method A prints 144.95 while B and C print 144.96 — adjacent cells of the declared 0.01° output grid, reached by different deterministic integer routes. That pair is pinned by the golden schedule across the tested host compiler/backend matrix; the embedded profiles separately pin their own scheduled outputs.
The same ladder in the machine's own digits — a pedagogical integer trace (rotation mode, computing sin 45° = cos 45°; shown in Q2.30-style values with micro-degree residuals for readability — the shipped library's actual state is Q16.48 over 48 iterations, against an angle table it builds once at init by integer series):
| i | σ | shift | x (cos path) | y (sin path) | z residual (µdeg) | table: atan 2−i |
|---|---|---|---|---|---|---|
| 0 | +1 | >>0 | 652032874 | 652032874 | 0 | 45000000 |
| 1 | +1 | >>1 | 326016437 | 978049311 | −26565051 | 26565051 |
| 2 | −1 | >>2 | 570528764 | 896545202 | −12528808 | 14036243 |
| 3 | −1 | >>3 | 682596914 | 825229107 | −5403792 | 7125016 |
| 4 | −1 | >>4 | 734173733 | 782566800 | −1827458 | 3576334 |
| 5 | −1 | >>5 | 758628945 | 759623871 | −37547 | 1789911 |
| … | each further row: one more bit of sin 45° = cos 45° = 0.7071… | |||||
Every cell is an integer. σ is the rotation direction (sign of z); there is no multiplication anywhere in the loop — only shifts, adds, and the angle table (48 entries in the shipped library). The same ladder run in vectoring mode gives atan2 and asin, which is how Zn comes out.
And one step further: Method C drops angles out of the hot path entirely. If the almanac publishes each body as an integer unit vector (Q2.30 — 30 fractional bits), the whole sight collapses to three dot products and a divide. Vega, from the built-in vector almanac:
$ ./sight_reduction --star 3 836136000000 Vega at UT1 J2000 +836136000000 ms: GHA Aries: 279.06 deg GHA: 359.61 deg dec: 38.81 deg earth-fixed vector: (836698133, 5757400, 672922426)/2^30
sin Hc = O · B as one integer dot product: the measured hot-path cost on a laptop is ~7 ns per sight against ~32 ns for the double/libm angle path. The 7 ns figure starts from prebuilt vectors and returns machine-native outputs; almanac construction and conversion back to human angles are deliberately outside that comparison. Page 3 tours the whole chain.
| AGE | NUMBERS ON THE “PAPER” | MULTIPLY STRATEGY | PRECISION MODEL | REPRODUCIBILITY CLAIM |
|---|---|---|---|---|
| Log tables | 5–6 digit integer mantissas | lookup + integer addition | declared: table step (~0.1′) | same lookups and rounding give the same arithmetic sum |
| Floating point | binary64: sign, exponent, 52-bit fraction | hardware arithmetic + platform libm | ~16 digits, error varies per operation | not guaranteed by C/libm alone |
| astro-nav-int | int64 fixed point (Q16.48, Q2.30, marcmin) | CORDIC shift/add; dot products | declared: 0.01° grid (0.6′ step, at most 0.30′ rounding), gated | committed schedules on the tested matrix |
| LAYER | METHOD | RESULT |
|---|---|---|
| Linked symbols | scan library, consumer, and embedded images for libm imports and compiler soft-float helpers | no floating-point imports |
| Disassembly | inspect every embedded image for floating-point opcodes | zero FP instructions |
| Cross-target execution | run 6 build profiles on Armv6-M, Cortex-M3, Cortex-M4 (built soft-float), RV32I, RV32IM under QEMU | all 30 per-profile hashes match the host bit-for-bit |
| Arithmetic backend | compare native __int128 against a portable two-limb C99 implementation |
bit-identical outputs |
| Resource envelope | measure freestanding images: flash, static RAM, painted-stack high-water | 53–107 KB flash · ≤416 B static RAM · <6 KB stack |