← INDEX

ONE SIGHT, THREE TRADITIONS

the same sight worked three ways — the navigator's log tables,
conventional floating-point software, and the machine-native integer core

The sight, fixed for all three workings: assumed position 40°N 74°W, body at GHA 60°, declination 20°N  →  local hour angle LHA 346° (meridian angle 14° E). Question: how high should it stand (Hc), and on what bearing (Zn)?
PAPER LOG TABLESTYPICAL FLOATING CASTRO-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
AGE OF SAIL

Five-digit logarithms, plain addition

fixed-point arithmetic, executed by a human

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.

QUANTITYTABLE ENTRY5-DIGIT MANTISSA
log hav 14°8.1717917179 (+8)
log cos 40°9.8842588425 (+9)
log cos 20°9.9729997299 (+9)
column sum28.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 table23°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.

FLOATING-POINT ERA

The same formula, handed to a math library

IEEE 754 binary64, sin/cos/asin/atan2 from libm

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.

MACHINE FIXED POINT

The navigator's arithmetic, run by the machine

astro-nav-int: int64 Q16.48, CORDIC shift/add, no FPU, no libm

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:

N E S W
STEPStart
SIGNED TURNnone
BEARING SO FAR0.000 deg
OFF TARGET144.952 deg

Needle at north. The dashed line is the azimuth this sight demands, 144.95° true. Every turn that follows uses one angle-table entry, two shifts, and additions or subtractions.

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σshiftx (cos path)y (sin path)z residual (µdeg)table: atan 2−i
0+1>>0652032874652032874045000000
1+1>>1326016437978049311−2656505126565051
2−1>>2570528764896545202−1252880814036243
3−1>>3682596914825229107−54037927125016
4−1>>4734173733782566800−18274583576334
5−1>>5758628945759623871−375471789911
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.

AGENUMBERS ON THE “PAPER”MULTIPLY STRATEGYPRECISION MODELREPRODUCIBILITY 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
VERIFICATION

The claim is checked on the artifact, not the source

a compiler can smuggle floating point in; so the compiled images are inspected
LAYERMETHODRESULT
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
So “integer-only” means: the claim is about what the machine executes. The shipping library and CLI contain no floating-point instructions and link no math library — audited at the instruction level, with matching profile hashes across the host and five QEMU target configurations. (The offline reference generators and the native-double validation build do use floating point; the claim is about the shipping runtime.) Conceptually it is not a rejection of the old ways but a return to one of their disciplines: traditional tabular sight reduction showed that the problem can be carried out with fixed-point arithmetic and well-chosen tables. This project keeps that discipline — declared quantization steps, published worked answers, reproducible sums — and swaps the human executing it for a small integer processor.