/** * Equivalence test: the TypeScript rewrite against the original Luminary099 * SPSIN / SPCOS, run on the Virtual AGC CPU emulator for every one of the * 32,768 possible 15-bit inputs (65,536 calls in all). * * Usage: node equivalence.test.ts */ import { readFileSync } from 'node:fs' import { spsin, spcos, naiveSin, naiveCos } from './spsin.ts' /** A 15-bit ones'-complement AGC word to an integer. Negative zero reads as 0. */ const word15 = (w: number) => (w & 0o40000 ? -(~w & 0o37777) : w) /** The emulator's 16-bit accumulator to an integer, refusing overflowed values. */ function acc16(a: number): number { if (((a >> 15) & 1) !== ((a >> 14) & 1)) throw new Error(`overflowed accumulator ${a.toString(8)}`) return a & 0o100000 ? -(~a & 0o37777) : a & 0o37777 } const rows = readFileSync(process.argv[2], 'utf8') .trim() .split('\n') .map((l) => l.split(' ').map((v) => parseInt(v, 8))) type Fn = (x: number) => number function compare(name: string, fn: Fn, column: 1 | 2) { let mismatches = 0 let worst = 0 const examples: string[] = [] for (const r of rows) { const x = word15(r[0]) const want = acc16(r[column]) const got = fn(x) if (got !== want) { mismatches++ worst = Math.max(worst, Math.abs(got - want)) if (examples.length < 3) examples.push(`x=${r[0].toString(8).padStart(5, '0')}: original ${want}, rewrite ${got}`) } } console.log(`${name.padEnd(22)} ${rows.length} inputs, ${mismatches} mismatches, worst ${worst} LSB`) for (const e of examples) console.log(` ${e}`) return mismatches } console.log('Naive rewrite (Math.sin / Math.cos):') compare(' sin', naiveSin, 1) compare(' cos', naiveCos, 2) console.log('Rewrite from intent (fixed-point, flight coefficients):') const failed = compare(' sin', spsin, 1) + compare(' cos', spcos, 2) process.exit(failed ? 1 : 0)