diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index c85b7678..e4e2340d 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -171,6 +171,7 @@ definition below depends on one, it says so. | **D-7** | `ROLL` semantics. | **Moot.** `ROLL` is retired under D-2. | | **D-8** | Q48.16 signedness. | **Signed.** v3's unsigned comparisons and `Q.FROM-INT` clamping are retired. | | **D-9** | Instruction word width on a 64-bit-cell host (ruled 2026-10-02). | **32 bits at every cell width.** Six 5-bit slots plus 2 spare bits, as in §1.2. On a 64-bit host the instruction word is the low 32 bits of the cell and the high half is ignored, so compiled code is identical at both widths. Only data and `@p` literals are a full cell wide. | +| **D-10** | Q48.16 width on a 64-bit-cell node (ruled 2026-10-02). | **Two cells at every cell width.** A Q value is a signed double on 32- and 64-bit nodes alike, so every Q word is the same double word at both widths. At 32-bit cells this is bit-for-bit v3's 64-bit Q. At 64-bit cells the low cell is v3's value, and where v3 wraps on Q48.16 overflow (a sum past Q max, `ABS` or `NEG` of Q min) the high cell carries the true result instead. | **Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting @@ -640,14 +641,14 @@ index should use them; they cost one instruction per iteration. ### 5.26 Q48.16 fixed-point math A Q48.16 value is 64 bits, so on a 32-bit node it occupies **two cells** and every Q word is a double -word. Per D-8, v4 Q values are signed. +word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values are signed. | Word | Fate | Notes | | --- | --- | --- | -| `Q.+` `Q.-` | CAP | `D+`, `D-` — executed on the golden model (2026-10-02) against v3's `q48_add`/`q48_sub` (`uint64_t` wrapping). At 32-bit cells the result is bit-for-bit v3's, overflow included. **Open:** at 64-bit cells a Q value as a sign-extended double has the same low cell as v3, but on Q48.16 overflow the high cell holds the true carry where v3 wraps. Whether a Q value is one cell or two on a 64-bit node is not yet ruled. | +| `Q.+` `Q.-` | CAP | `D+`, `D-` — executed on the golden model (2026-10-02) against v3's `q48_add`/`q48_sub` (`uint64_t` wrapping): bit-for-bit at 32-bit cells; at 64-bit cells the low cell is v3's, per D-10. | | `Q.*` | CAP | 64×64 product from four `UM*` partial products, shifted right 16. | | `Q./` | CAP | Shifted long division. **Division by zero sets an error** (v3 returned 0 silently). | -| `Q.ABS` `Q.NEG` | CAP | `DABS`, `DNEGATE` | +| `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.LOG` `Q.EXP` `Q.SQRT` `Q.SIN` `Q.COS` | CAP | Algorithms ported from `q48_words.c`; the hosted C versions are the golden model. | | `Q.FROM-INT` | CAP | `S>D` shifted left 16. Negative values are no longer clamped to 0. | | `Q.TO-INT` | CAP | Shift right 16, take the low cell. | diff --git a/v4/tests/test_foundation.c b/v4/tests/test_foundation.c index 4387b674..a8512021 100644 --- a/v4/tests/test_foundation.c +++ b/v4/tests/test_foundation.c @@ -15,8 +15,9 @@ * built as q*n + r. D+ is the call-free version in 5.7, checked against C * over every quadruple of the edge vectors and pseudo-random pairs. /MOD, * U>, ABS, S>D, DABS, M+, D-, D0= and D= are as written in section 5 and - * checked against C. Q.+ and Q.- (section 5.26: the D+ and D- words) are - * checked against v3's 64-bit wrapping q48_add and q48_sub. Every word is + * checked against C. Q.+, Q.-, Q.ABS and Q.NEG (section 5.26: the D+, D-, + * DABS and DNEGATE words) are checked against v3's 64-bit q48_add, q48_sub, + * q48_abs and 0 - q, with Q values two cells wide at both widths (D-10). Every word is * also probed for how much of the 10- and 9-deep circular stacks (D-2) it * leaves to its caller. * @@ -498,6 +499,28 @@ static int q_matches_v3(v4_cell word, uint64_t a, uint64_t b, uint64_t v3) #endif } +/* The same for the one-operand Q words: Q.ABS (the DABS word) and Q.NEG + * (the DNEGATE word). `wl`, `wh` is the exact double result, which at + * 64-bit cells may differ from v3 in the high cell only (D-10). */ +static int q1_matches_v3(v4_cell word, uint64_t a, uint64_t v3) +{ + v4_cell al, ah, rl, rh; + v4_ucell xl, xh; + qcells(a, &al, &ah); + if (!call(word, 2, al, ah, 0)) return 0; +#if V4_CELL_BITS == 32 + (void)xl; (void)xh; + qcells(v3, &rl, &rh); + return left2(rl, rh); +#else + if (word == w_dnegate || ah < 0) dneg((v4_ucell)al, (v4_ucell)ah, &xl, &xh); + else { xl = (v4_ucell)al; xh = (v4_ucell)ah; } + rl = (v4_cell)(v4_ucell)v3; + rh = (v4_cell)xh; + return (v4_ucell)rl == xl && left2(rl, rh); +#endif +} + /* 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 @@ -797,6 +820,23 @@ int main(void) CHECK(q_matches_v3(w_dminus, a, b, a - b), "Q.- random [%u]", i); } printf(" Q.+ random cases that overflow Q48.16: %u of 20000\n", over); + + /* Q.ABS and Q.NEG against v3's q48_abs and 0 - q. */ + for (unsigned i = 0; i < nq; i++) { + uint64_t q = qv[i]; + CHECK(q1_matches_v3(w_dabs, q, (q < ((uint64_t)1 << 63)) ? q : (uint64_t)0 - q), + "Q.ABS [%u]", i); + CHECK(q1_matches_v3(w_dnegate, q, (uint64_t)0 - q), "Q.NEG [%u]", i); + } + for (unsigned i = 0; i < 20000; i++) { + uint64_t q; + x ^= x << 13; x ^= x >> 7; x ^= x << 17; q = x; + if (i & 1u) q >>= (unsigned)(x & 63u); + if (i & 2u) q = (uint64_t)0 - q; + CHECK(q1_matches_v3(w_dabs, q, (q < ((uint64_t)1 << 63)) ? q : (uint64_t)0 - q), + "Q.ABS random [%u]", i); + CHECK(q1_matches_v3(w_dnegate, q, (uint64_t)0 - q), "Q.NEG random [%u]", i); + } } /* Stack headroom of the two longest definitions. */