diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index e4ad39bf..3bb171be 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -676,7 +676,7 @@ word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values | `(QE)` | CAP | Variable, 8 cells: `Q.EXP`'s `x`, term, sum, sign and term index. | | `Q.FROM-INT` | CAP | `push 0 a! 0 pop 15 FOR +* UNEXT push drop a pop` — `n * 2^16` as a signed double. `+*` with `S = 0` never adds, so each step is an exact arithmetic right shift of `T:A`; starting from `T:A = n:0` (that is, `n * 2^N`) and shifting `N-16` bits leaves `n * 2^16`. The count is `N-17`: 15 at 32-bit cells, 47 at 64. Negative values are no longer clamped to 0. Executed on the golden model (2026-10-02): matches v3's `q48_from_u64` for `n >= 0` (at 64-bit cells in the low cell, where v3 wraps for `n >= 2^47`, per D-10). Clobbers `A`. | | `Q.TO-INT` | CAP | `push a! 0 pop 15 FOR +* UNEXT drop drop a` — the same `+*` shift, 16 bits at every cell width, leaving the low cell in `A`. Rounds toward minus infinity, as v3's arithmetic shift does (−1.5 gives −2). Executed on the golden model (2026-10-02): v3's `q48_to_u64` exactly at 64-bit cells, its low cell at 32. Clobbers `A`. | -| `Q.1` `Q.0` `Q.SCALE` | IN | Double-cell constants. | +| `Q.1` `Q.0` `Q.SCALE` | IN | Double-cell constants, placed in line as two literals, low cell first: `Q.1` and `Q.SCALE` are `65536 0` (1.0), `Q.0` is `0 0`. v3's are 65536, 0 and 65536. Executed on the golden model (2026-10-03): the values match v3's, `Q.1 Q.TO-INT` is 1, `1 Q.FROM-INT` is `Q.1`, and `q Q.1 Q.*` and `q Q.0 Q.+` return `q`. | | `Q.=` `Q.<` `Q.>` `Q.0=` `Q.MAX` `Q.MIN` | CAP | `D=`, `D<`, `2SWAP D<` (for `Q.>`), `D0=`, `DMAX`, `DMIN`. Signed (D-8); v3 compared unsigned, so v3 agrees only when both values have the same sign. Executed on the golden model (2026-10-02). `Q.>` was given here as `SWAP D<`, which swaps single cells, not Q values, and gave a wrong answer in 11702 of 20169 test cases. | | `Q.PRINT` | CAP | Pictured output, five fractional digits. | diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index 31f99aab..c6b0b1d9 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -34,7 +34,8 @@ * for 0 with NODE-ERROR at or below zero. * (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 + * size and sign. The in-line constants Q.1, Q.0 and Q.SCALE are checked + * against v3's values and in use. Every word is * also probed for how much of the 10- and 9-deep circular stacks (D-2) it * leaves to its caller. * @@ -79,7 +80,8 @@ 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_qcos; + w_qreduce, w_qsin, l_trig, w_qcos, + w_q1, w_q0, w_qscale, w_q1_times, w_q0_plus, w_q1_toint, w_one_fromint; #define O(name) v4_asm_op(&as, V4_OP_##name) #define LIT(v) v4_asm_lit(&as, (v4_cell)(v)) @@ -1301,6 +1303,28 @@ static void build(void) #undef HERE_ #undef FWD + /* Q.1, Q.0, Q.SCALE 5.26, fate IN + * In-line double-cell constants: the compiler places two literals, low + * cell first. Q.1 and Q.SCALE are `65536 0` (1.0), Q.0 is `0 0`; v3's + * are 65536, 0 and 65536. Each expansion is assembled here followed by + * `;` so that it can be run, and then in the middle of a definition, as + * it would be used: + * Q.1 Q.* ( q -- q ) Q.0 Q.+ ( q -- q ) + * Q.1 Q.TO-INT ( -- 1 ) 1 Q.FROM-INT ( -- q ), which must be Q.1 */ +#define Q_ONE() do { LIT(65536); LIT(0); } while (0) +#define Q_ZERO() do { LIT(0); LIT(0); } while (0) +#define Q_SCALE() do { LIT(65536); LIT(0); } while (0) + w_q1 = v4_asm_label(&as); Q_ONE(); O(SEMI); + w_q0 = v4_asm_label(&as); Q_ZERO(); O(SEMI); + w_qscale = v4_asm_label(&as); Q_SCALE(); O(SEMI); + w_q1_times = v4_asm_label(&as); Q_ONE(); CALL(w_qstar); O(SEMI); + w_q0_plus = v4_asm_label(&as); Q_ZERO(); CALL(w_dplus); O(SEMI); + w_q1_toint = v4_asm_label(&as); Q_ONE(); CALL(w_qtoint); O(SEMI); + w_one_fromint = v4_asm_label(&as); LIT(1); CALL(w_qfromint); O(SEMI); +#undef Q_SCALE +#undef Q_ZERO +#undef Q_ONE + CHECK(v4_asm_ok(&as), "foundation words assemble"); } @@ -2540,6 +2564,31 @@ int main(void) } } + /* Q.1, Q.0 and Q.SCALE: v3's values, and how they behave in use. */ + { + v4_cell ol, oh, zl, zh; + uint64_t x = 0x8EBC6AF09C88C6E3u; + qcells(65536u, &ol, &oh); /* v3 Q.1 and Q.SCALE */ + qcells(0u, &zl, &zh); /* v3 Q.0 */ + CHECK(call(w_q1, 0, 0, 0, 0) && left2(ol, oh), "Q.1 is v3's 65536"); + CHECK(call(w_q0, 0, 0, 0, 0) && left2(zl, zh), "Q.0 is v3's 0"); + CHECK(call(w_qscale, 0, 0, 0, 0) && left2(ol, oh), "Q.SCALE is v3's 65536"); + CHECK(call(w_q1_toint, 0, 0, 0, 0) && left1(1), "Q.1 Q.TO-INT is 1"); + CHECK(call(w_one_fromint, 0, 0, 0, 0) && left2(ol, oh), "1 Q.FROM-INT is Q.1"); + for (unsigned i = 0; i < 2000; i++) { + uint64_t q; + v4_cell ql, qh; + x ^= x << 13; x ^= x >> 7; x ^= x << 17; + q = asr64(x, (unsigned)(x & 63u)); + if (i == 0) q = 0; + if (i == 1) q = ((uint64_t)1 << 63) - 1u; + if (i == 2) q = (uint64_t)1 << 63; + qcells(q, &ql, &qh); + CHECK(call(w_q1_times, 2, ql, qh, 0) && left2(ql, qh), "q Q.1 Q.* is q [%u]", i); + CHECK(call(w_q0_plus, 2, ql, qh, 0) && left2(ql, qh), "q Q.0 Q.+ is q [%u]", i); + } + } + /* Q./ at the overflow boundary: |a| * 2^16 against |b| * 2^(2N-1), for * divisors around 2^16 and 2^17, every sign. */ {