test(v4.0.0): D-10 two-cell Q48.16; execute Q.ABS and Q.NEG against v3
D-10 (ruled 2026-10-02): a Q48.16 value is two cells at every cell width, so every Q word is the same double word on 32- and 64-bit nodes. Recorded in DECOMPOSITION.md section 3 and 5.26; the open note on the Q.+/Q.- row is resolved by it. Q.ABS and Q.NEG are the DABS and DNEGATE words. Checked against v3's q48_abs and 0 - q on the 15 edge Q values and 20000 pseudo-random values, at 32- and 64-bit cells, optimised and ASan+UBSan: - 32-bit cells: bit-for-bit v3, including v3's wrap of Q min to itself. - 64-bit cells: the low cell is v3's; the high cell is the true sign (so ABS and NEG of Q min give +2^63 rather than wrapping), per D-10. Breaking DNEGATE's carry fails Q.NEG, Q.ABS and Q.- checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4f493fa317
commit
d948e0c1a3
@@ -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. |
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user