SWI 0x09 — ArcTan¶
- Entry:
0x00000474(ARM) - Status: verified (hardware-checked 2026-07-08: cycle counts + full-range 65536-input dump matching the documented algorithm bit-for-bit)
Summary¶
Fixed-point arctangent of a single Q1.14 value. Evaluates an 8-term odd
polynomial approximation of atan(x) using Horner's scheme with fixed-point
(Q14) multiplies. Accurate to a fraction of a degree only for |x| <= 1.0;
it diverges badly outside that range (see Edge cases). ArcTan2 (SWI 0x0A) exists
precisely to keep the argument within the valid range.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | s16/Q1.14 | Tangent value: 1 sign bit, 1 integer bit, 14 fraction bits (0x4000 = 1.0) |
Returns¶
| Reg | Out | Meaning |
|---|---|---|
| r0 | s | Angle in units where 0x10000 = 360°, i.e. 0x4000 = 90°. For valid inputs the result lies in about -0x2000..0x2000 (±45°) |
Clobbered registers¶
r1, r3 are scratch. Only r0 is meaningful on return.
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 = result; r1/r3 hold internal leftovers (0xFFFFFC00 / 0x9FB3 observed); r2 preserved. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Algorithm¶
Hardware-exact: with 32-bit wraparound on every multiply and arithmetic-shift
truncation as written below, this algorithm reproduces the real BIOS output
bit-for-bit for all 65536 inputs (full-range dump, 2026-07-08 —
results/arctan_full_dump.csv). The 32-bit MUL wraparound matters: in the
divergent region s*t overflows 32 bits, which is what produces the chaotic
outputs there.
Let x be the Q1.14 input. First compute t = -(x*x >> 14) (a Q14 value equal
to -x^2). Then evaluate a polynomial P(t) by Horner's method, seeding an
accumulator and, at each step, doing acc = (acc * t) >> 14 followed by adding
the next coefficient. Finally the result is (P(t) * x) >> 16.
Because t = -x^2, the alternating signs of the arctan Taylor-like series are
produced automatically, making this an odd polynomial in x.
Extracted coefficients (raw immediate constants, in Horner order)¶
The add immediates are assembled from the split ARM immediates in the binary:
| Step | Constant (dec) | Constant (hex) |
|---|---|---|
| seed | 169 | 0x00A9 |
| + | 912 | 0x0390 |
| + | 2332 | 0x091C (0x900 + 0x1C) |
| + | 4022 | 0x0FB6 (0xF00 + 0xB6) |
| + | 5802 | 0x16AA (0x1600 + 0xAA) |
| + | 8321 | 0x2081 (0x2000 + 0x81) |
| + | 13905 | 0x3651 (0x3600 + 0x51) |
| + | 41721 | 0xA2F9 (0xA200 + 0xF9) |
Equivalently, with s = acc starting at 169:
s = 169
s = (s*t >> 14) + 912
s = (s*t >> 14) + 2332
s = (s*t >> 14) + 4022
s = (s*t >> 14) + 5802
s = (s*t >> 14) + 8321
s = (s*t >> 14) + 13905
s = (s*t >> 14) + 41721
result = (s * x) >> 16
0xA2F9 / 0x10000 * 360° ≈ 57.29°/rad, the radian-to-this-unit factor).
Edge cases & known bugs¶
- Valid range
|x| <= 1.0(0x4000): near-perfect. Hardware dump:x=0.25 → 14.04°(true 14.04°),x=0.5 → 26.56°(true 26.57°),x=1.0 → exactly 45.000°. Max absolute error over the whole[-1,+1]range is 1.35 output units (0.0074°), at x=0x3FE8. The output is monotonic in this range except for 6 single-unit dips (e.g. at 0x3F0A, 0x3F14, and mirrored points; alsof(0x4001) < f(0x4000)). - Known inaccuracy —
|x| > 1.0(full-range hardware dump, 2026-07-08): the polynomial was fitted only for|x| <= 1; beyond that it degrades, then the 32-bit overflow ofs*tmakes it chaotic. Max |error| per band:
| Input band | Max error | At |
|---|---|---|
| 1.00–1.25 | 0.12° | x=1.25 |
| 1.25–1.50 | 6.95° | x≈1.4995 |
| 1.50–1.75 | 153.5° | x=1.75 (returns −93.27°) |
| 1.75–2.00 | 242.77° | x≈1.9873 |
Error stays below 1° up to x=1.374 (0x57EF) and below 10° up to x=1.527
(0x61B9). From x≈1.666 (0x6A9F) the output's sign flips 12 times across
the rest of the positive range, swinging over the full ±0x8000 span
(+32758 at x≈1.792, −32768 two inputs earlier). x=1.75 → −93.27° and the
~243° peak error predicted by static analysis are confirmed exactly.
The error source is the polynomial's limited fit domain, compounded by
the repeated asr #14 truncation after each multiply and, past x≈1.6, by
32-bit overflow of the Horner accumulator product.
- Not an odd function: f(−x) = −f(x) − 1 for 32760 of the 32767 ± pairs.
The asr truncations floor toward −∞, leaving a systematic 1-unit (0.0055°)
negative bias — e.g. ArcTan(0x1000) = +0x9FB but ArcTan(−0x1000) = −0x9FC.
Exactly 7 inputs are symmetric (f(−x) = −f(x)): 0x0F38, 0x2000 (x=0.5),
0x2A50, 0x4000 (x=1.0), 0x4B40, 0x6000 (x=1.5), 0x79F0 — so the headline
values ArcTan(±1.0) = ±0x2000 happen to look odd-symmetric.
- Because callers should keep |tan| <= 1, the practical primitive is ArcTan2,
which always feeds ArcTan a ratio with magnitude ≤ 1.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
Constant 100 cycles for every nonzero input tested (0x1000, 0x4000,
0x7FFF, -0x8000); 98 for input 0. Confirms the fixed Horner evaluation with
no data-dependent branching.
Open questions (need hardware verification)¶
None. The full-range hardware dump (results/arctan_full_dump.csv, all 65536
inputs) matches the extracted algorithm bit-for-bit, which pins down the error
curve, the divergence/sign-flip profile, and the rounding behavior exactly.
GBATEK cross-reference¶
GBATEK notes ArcTan "has a hidden inaccuracy" but does not give coefficients or
the error curve. This document adds the eight extracted polynomial coefficients,
the exact Q14 fixed-point evaluation scheme, the measured accuracy inside
|x|<=1, and the concrete divergence (and its cause) outside that range.