test(v4.0.0): execute Q.+ and Q.- against v3 Q48.16

DECOMPOSITION.md 5.26 makes Q.+ and Q.- the D+ and D- words. They are
checked against v3's q48_add/q48_sub (uint64_t a + b, a - b, wrapping)
on every pair of 15 edge Q values (0, ulp, 0.5, 1.0, 1.5, -1.0, -ulp,
values across the 32-bit seam, Q max and min, +-12345.0) and 20000
pseudo-random pairs, 2682 of which overflow Q48.16.

- 32-bit cells: bit-for-bit v3's result, overflow wrap included.
- 64-bit cells, with a Q value as a sign-extended double: the low cell is
  v3's result; on 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
  ruled; recorded as open in 5.26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-02 22:52:27 -04:00
co-authored by Claude Opus 5.5
parent 6a371b850e
commit 5ac52f19d6
2 changed files with 73 additions and 1 deletions
+1 -1
View File
@@ -644,7 +644,7 @@ word. Per D-8, v4 Q values are signed.
| Word | Fate | Notes |
| --- | --- | --- |
| `Q.+` `Q.-` | CAP | `D+`, `D-` |
| `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.*` | 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` |
+72
View File
@@ -25,6 +25,7 @@
#include "v4/asm.h"
#include "v4/testcode.h"
#include "v4/umul.h"
#include <stdint.h>
#include <stdio.h>
static int failures = 0, checks = 0;
@@ -455,6 +456,46 @@ static int smrem_exact(v4_cell q, v4_cell nn, v4_cell r)
return call(w_smrem, 3, (v4_cell)dl, (v4_cell)dh, nn) && left2(r, q);
}
/* Q48.16 (section 5.26). v3's Q.+ and Q.- are uint64_t a + b and a - b,
* wrapping (v3/include/q48_16.h). Here a Q value is a double: at 32-bit
* cells its two halves, at 64-bit cells the value sign-extended (D-8). */
static void qcells(uint64_t q, v4_cell *lo, v4_cell *hi)
{
#if V4_CELL_BITS == 32
*lo = (v4_cell)(v4_ucell)(q & 0xFFFFFFFFu);
*hi = (v4_cell)(v4_ucell)(q >> 32);
#else
*lo = (v4_cell)(v4_ucell)q;
*hi = (q >> 63) ? V4_ALL_ONES : 0;
#endif
}
/* Run Q.+ (the D+ word) or Q.- (the D- word) on a and b and compare with
* v3's 64-bit wrapping result. At 32-bit cells the double must equal v3's
* value bit for bit. At 64-bit cells the low cell must equal v3's value and
* the high cell must be the true sign of the unwrapped result. */
static int q_matches_v3(v4_cell word, uint64_t a, uint64_t b, uint64_t v3)
{
v4_cell al, ah, bl, bh, rl, rh;
qcells(a, &al, &ah);
qcells(b, &bl, &bh);
if (!call4(word, al, ah, bl, bh)) return 0;
#if V4_CELL_BITS == 32
qcells(v3, &rl, &rh);
return left2(rl, rh);
#else
{
v4_ucell xl, xh, nl, nh;
if (word == w_dminus) { dneg((v4_ucell)bl, (v4_ucell)bh, &nl, &nh); }
else { nl = (v4_ucell)bl; nh = (v4_ucell)bh; }
dadd((v4_ucell)al, (v4_ucell)ah, nl, nh, &xl, &xh);
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
@@ -725,6 +766,37 @@ int main(void)
}
}
/* Q.+ and Q.- (section 5.26: the D+ and D- words) against v3. */
{
static const uint64_t qv[] = {
0, 1, 0x8000u, 0x10000u, 0x18000u, /* 0, ulp, 0.5, 1.0, 1.5 */
(uint64_t)0 - 0x10000u, (uint64_t)0 - 1u, /* -1.0, -ulp */
0xFFFFu, 0x10001u, 0xFFFFFFFFu, 0x100000000u, /* across the 32-bit seam */
((uint64_t)1 << 63) - 1u, (uint64_t)1 << 63, /* Q max, Q min */
(uint64_t)12345 << 16, (uint64_t)0 - ((uint64_t)12345 << 16)
};
const unsigned nq = sizeof qv / sizeof qv[0];
uint64_t x = 0x9E3779B97F4A7C15u;
unsigned over = 0;
for (unsigned i = 0; i < nq; i++)
for (unsigned j = 0; j < nq; j++) {
CHECK(q_matches_v3(w_dplus, qv[i], qv[j], qv[i] + qv[j]), "Q.+ [%u,%u]", i, j);
CHECK(q_matches_v3(w_dminus, qv[i], qv[j], qv[i] - qv[j]), "Q.- [%u,%u]", i, j);
}
for (unsigned i = 0; i < 20000; i++) {
uint64_t a, b;
x ^= x << 13; x ^= x >> 7; x ^= x << 17; a = x;
x ^= x << 13; x ^= x >> 7; x ^= x << 17; b = x;
if (i & 1u) a >>= (unsigned)(x & 63u); /* mixed magnitudes */
if (i & 2u) b = (uint64_t)0 - b;
if (((a + b) ^ a) & ((a + b) ^ b) & ((uint64_t)1 << 63)) over++;
CHECK(q_matches_v3(w_dplus, a, b, a + b), "Q.+ random [%u]", i);
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);
}
/* Stack headroom of the two longest definitions. */
{
static const v4_cell umstar_args[][4] = {