feat(v4.0.0): Q.COS, bit for bit with v3

Q.COS is v3's q48_cos_approx: on |(Q.REDUCE) x|, the even Taylor series
to n = 10. It sets term and sum to 1.0, n to 2 and the sign cell to 0,
then jumps into Q.SIN's loop, so it adds no call level.

Checked bit for bit against v3's q48_cos_approx (ported into the test)
on the same 33 edge angles and 3000 pseudo-random angles as Q.SIN, at
32- and 64-bit cells, optimised and ASan+UBSan (`make test`,
`make sanitize`). Mutating the start index, the start sum, the
alternation or the sign cell each fails 2500+ cosine checks and no sine
checks.

Headroom (data under arg / return): Q.COS 3/2.

Test results on the amd64 host only. This is a development check, not
acceptance (JUSTIFICATION.md section 16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-03 10:53:52 -04:00
co-authored by Claude Opus 5.5
parent 1ce705783c
commit 8235078b3b
2 changed files with 53 additions and 8 deletions
+50 -5
View File
@@ -32,8 +32,9 @@
* 0 <= q < 2^48, and for 0 with NODE-ERROR below zero (D-12).
* Q.LOG is checked bit for bit against v3's q48_log_approx for x > 0, and
* for 0 with NODE-ERROR at or below zero.
* (Q.REDUCE) and Q.SIN are checked bit for bit against v3's
* q48_reduce_angle and q48_sin_approx on angles of every size and sign. Every word is
* (Q.REDUCE), Q.SIN and Q.COS are checked bit for bit against v3's
* q48_reduce_angle, q48_sin_approx and q48_cos_approx on angles of every
* size and sign. Every word is
* also probed for how much of the 10- and 9-deep circular stacks (D-2) it
* leaves to its caller.
*
@@ -78,7 +79,7 @@ static v4_cell w_nip, w_swap, w_or, w_negate, w_rot, w_zless, w_zequal,
w_slashmod, w_mplus, w_dminus, w_d0equal, w_dequal,
w_qfromint, w_qtoint, w_less, w_equal, w_dless, w_2swap, w_2over,
w_dmax, w_dmin, w_qgt_doc, w_qgt, w_dltkeep, w_qstar, w_d2starc, w_uqdiv, w_qslash, w_qexp, w_qsqrt, w_qlog,
w_qreduce, w_qsin, l_trig;
w_qreduce, w_qsin, l_trig, w_qcos;
#define O(name) v4_asm_op(&as, V4_OP_##name)
#define LIT(v) v4_asm_lit(&as, (v4_cell)(v))
@@ -1273,6 +1274,27 @@ static void build(void)
O(DROP); LIT(-1); O(SEMI);
HERE_(sp); O(DROP); LIT(0); O(SEMI);
}
/* : Q.COS ( x -- cos x ) 5.26, v3's q48_cos_approx
* On xu = |(Q.REDUCE) x|: 1 - xu^2/2! + xu^4/4! - ... to n = 10, by
* Q.SIN's loop: term and sum start at 1.0, n at 2, and the sign cell is
* 0 so nothing is negated at the end.
* (Q.REDUCE) -if A1 inv 1 + A1: xu
* 0 QT b! !b 0 over 0 Q.* drop QT 1 + b! !b x^2
* 65536 QT 2 + b! !b 65536 QT 3 + b! !b
* 2 QT 4 + b! !b -1 QT 5 + b! !b jump TRIG
* Clobbers A and B. */
{
v4_asm_ref a1;
w_qcos = v4_asm_label(&as);
CALL(w_qreduce);
a1 = FWD(MINUS_IF); O(INV); LIT(1); O(ADD); HERE_(a1);
LIT(0); VSET(QT);
LIT(0); O(OVER); LIT(0); CALL(w_qstar); O(DROP); VSET(QT + 1);
LIT(65536); VSET(QT + 2); LIT(65536); VSET(QT + 3);
LIT(2); VSET(QT + 4); LIT(-1); VSET(QT + 5);
JUMP_TO(l_trig);
}
#undef VSET
#undef VGET
#undef JUMP_TO
@@ -1748,6 +1770,24 @@ static uint64_t v3_q48_sin(uint64_t q)
return negative ? (uint64_t)0 - result : result;
}
/* v3's q48_cos_approx, verbatim but for the names. */
static uint64_t v3_q48_cos(uint64_t q)
{
int64_t x = v3_q48_reduce((int64_t)q);
uint64_t xu = (uint64_t)(x < 0 ? -x : x);
uint64_t x2 = v3_q48_mul(xu, xu);
uint64_t term = 65536, result = term;
int subtract = 1, nn;
for (nn = 2; nn <= 10; nn += 2) {
term = v3_q48_mul(term, x2);
term = v3_q48_div(term, (uint64_t)(nn * (nn - 1)) << 16);
result = subtract ? result - term : result + term;
subtract = !subtract;
if (term < 10) break;
}
return result;
}
/* Stack headroom. Run `word` with `dfill` marked cells under the canary and
* `rfill` marked cells under its return address, and report whether the
* results, the canary and every marked cell come back intact. The F18 stacks
@@ -2468,8 +2508,9 @@ int main(void)
}
}
/* (Q.REDUCE) and Q.SIN against v3's q48_reduce_angle and q48_sin_approx,
* bit for bit, on angles of every size and sign. */
/* (Q.REDUCE), Q.SIN and Q.COS against v3's q48_reduce_angle,
* q48_sin_approx and q48_cos_approx, bit for bit, on angles of every size
* and sign. */
{
static const int64_t qa[] = {
0, 1, -1, 0x8000, 0x10000, -0x10000, 102943, 102944, -102943, -102944,
@@ -2494,6 +2535,8 @@ int main(void)
"(Q.REDUCE) [%u] x=%lld", i, (long long)q);
qcells(v3_q48_sin((uint64_t)q), &el, &eh);
CHECK(call(w_qsin, 2, xl, xh, 0) && left2(el, eh), "Q.SIN [%u] x=%lld", i, (long long)q);
qcells(v3_q48_cos((uint64_t)q), &el, &eh);
CHECK(call(w_qcos, 2, xl, xh, 0) && left2(el, eh), "Q.COS [%u] x=%lld", i, (long long)q);
}
}
@@ -2623,6 +2666,8 @@ int main(void)
headroom("(Q.REDUCE)", w_qreduce, 2, qtrig_args, 6, 1, &dh, &rh);
headroom("Q.SIN", w_qsin, 2, qtrig_args, 6, 2, &dh, &rh);
CHECK(dh >= 2 && rh >= 2, "Q.SIN leaves room");
headroom("Q.COS", w_qcos, 2, qtrig_args, 6, 2, &dh, &rh);
CHECK(dh >= 2 && rh >= 2, "Q.COS leaves room");
headroom("SM/REM", w_smrem, 3, smrem_args, 5, 2, &dh, &rh);
CHECK(rh >= 1, "SM/REM leaves room for /MOD's return address");
headroom("/MOD", w_slashmod, 2, slashmod_args, 4, 2, &dh, &rh);