diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 895cbd90..e4ad39bf 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -147,8 +147,8 @@ IF body1 ELSE body2 THEN → if L1 drop body1 jump L2 times, as on the F18. `FOR ... UNEXT` is the same but the body must fit in one instruction word. **Register conventions.** `A` and `B` are caller-saved. A word that uses them says so. Words in this -document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN SEND RECV`. Words that clobber `B`: -`Q./ Q.EXP Q.SQRT Q.LOG Q.SIN SEND RECV`. +document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS SEND RECV`. Words that clobber `B`: +`Q./ Q.EXP Q.SQRT Q.LOG Q.SIN Q.COS SEND RECV`. **Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN. @@ -664,7 +664,7 @@ word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values | `D2*C` | CAP | `( lo hi cin -- lo' hi' cout )`: the double shifted left one bit, `cin` entering at the bottom and `cout` the bit leaving the top (both 0 or 1). `a! dup -if L1 drop 1 jump L2 L1: drop 0 L2: push over -if L3 drop 1 jump L4 L3: drop 0 L4: over + + push 2* a + pop pop` — `cin` waits in `A`, and `2hi + m` is `over + +`, so it needs one return entry. Executed on the golden model (2026-10-02). Clobbers `A`. | | `(Q/)` | CAP | Variable, 5 cells: `Q./`'s quotient register, divisor and result sign. Like `BASE` and the hold buffer, it lives in node memory. | | `Q.ABS` `Q.NEG` | CAP | `DABS`, `DNEGATE` — executed on the golden model (2026-10-02) against v3's `q48_abs` and `0 - q`: bit-for-bit at 32-bit cells, including v3's wrap of Q min to itself; at 64-bit cells the low cell is v3's and the high cell is the true sign, per D-10. | -| `Q.COS` | CAP | Algorithm ported from `q48_words.c`; the hosted C version is the golden model. | +| `Q.COS` | CAP | v3's `q48_cos_approx`: on `xu = |(Q.REDUCE) x|`, `1 - xu^2/2! + xu^4/4! - …` to `n = 10`, run by `Q.SIN`'s loop: `(Q.REDUCE) -if A1 inv 1 + A1: 0 (QT) b! !b 0 over 0 Q.* drop (QT) 1 + b! !b 65536 (QT) 2 + b! !b 65536 (QT) 3 + b! !b 2 (QT) 4 + b! !b -1 (QT) 5 + b! !b jump TRIG` — term and sum start at 1.0, `n` at 2, and the sign cell is 0 so nothing is negated at the end. It jumps into the loop rather than calling it, so it costs no extra return entry. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. | | `Q.SIN` | CAP | v3's `q48_sin_approx`: on `r = (Q.REDUCE) x` and `xu = |r|`, `xu - xu^3/3! + xu^5/5! - …` to `n = 11`, each term the last times `xu^2` over `n(n-1)`, stopping once a term is below 10 ulp; negated if `r < 0`. After the reduction every value fits one cell, so the variable `(QT)` holds single cells and the only calls are `(Q.REDUCE)`, `Q.*`, `Q./` and `UM*`. The Taylor loop is written so that `Q.COS` can jump into it. Executed on the golden model (2026-10-03): bit for bit v3's result on angles of every size and sign. Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. | | `(Q.REDUCE)` | CAP | `( x -- r )`, v3's `q48_reduce_angle`: the angle reduced to one cell, `-pi <= r <= pi` with `pi = 205887`, `2pi = 411774` — the remainder of `x / 2pi` with the sign of `x`, then one step of `2pi` back into range. `x / 2pi` does not fit a cell, but only the remainder is wanted: `|x| mod 2pi` is two `UM/MOD` steps, the high cell first and its remainder then leading the low cell. `dup (QT) b! !b -if L1 DNEGATE L1: 0 411774 UM/MOD drop 411774 UM/MOD drop (QT) b! @b -if P1 drop inv 1 + jump J1 P1: drop J1: dup -205888 + -if BIG drop dup 205887 + -if OK drop 411774 + ; OK: drop ; BIG: drop -411774 + ;` Executed on the golden model (2026-10-03), bit for bit v3's. Clobbers `A` and `B`. | | `(QT)` | CAP | Variable, 6 cells: `Q.SIN` / `Q.COS`'s sign, `x^2`, term, sum, `n` and subtract flag. | diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index 197ba5d1..31f99aab 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -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);