feat(v4.0.0): Q.EXP bit for bit with v3; lighter D2*C and (UQ/)
Q.EXP is v3's q48_exp_approx: the Taylor series to 10 terms on |q|, stopping below 50 ulp, 1.0 Q./ e^|q| for q < 0, e^0 = 1.0, and for |q| >= 16.0 either 0 or Q max (v3 returned all ones, which is -ulp read signed under D-8). x, term, sum, sign and term index live in a new 8-cell variable (QE). Checked bit for bit against v3's q48_exp_approx (ported into the test) on 23 edge values and 3000 pseudo-random q in (-16.0, 16.0), at both cell widths, optimised and ASan+UBSan; dropping a term, moving the 50-ulp stop or skipping the reciprocal all fail. Q.EXP calls Q./, which calls (UQ/), which calls D2*C, and at first it left its caller no return entries. Lightened along the chain: - D2*C takes cin in A and builds 2hi+m with `over + +`: no ROT, no SWAP. - (UQ/) parks the quotient bit instead of using ROT, stores q without SWAP, and does its full trial subtraction (with borrow) in line instead of calling DNEGATE D+. - Q.EXP keeps its term index in (QE) instead of a FOR count. Headroom (data under args / return): (UQ/) 4/3 -> 4/4, Q./ 3/2 -> 3/3, Q.EXP 3/2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9e03bdab19
commit
29cf2688d2
@@ -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./ SEND RECV`. Words that clobber `B`:
|
||||
`Q./ SEND RECV`.
|
||||
document that clobber `A`: `@ ! +! -! 2@ 2! C@ C! UM* * UM/MOD Q.FROM-INT Q.TO-INT Q.* Q./ Q.EXP SEND RECV`. Words that clobber `B`:
|
||||
`Q./ Q.EXP SEND RECV`.
|
||||
|
||||
**Return-stack words** (`>R R> R@ 2>R 2R> 2R@ I J UNLOOP` and the loop runtimes) are always IN.
|
||||
|
||||
@@ -658,12 +658,14 @@ word. Per D-10 it occupies two cells on a 64-bit node too. Per D-8, v4 Q values
|
||||
| --- | --- | --- |
|
||||
| `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 | `SWAP push over over UM* drop push push over pop -if L1 SWAP jump L2 L1: SWAP drop 0 L2: pop SWAP - push push over pop UM* pop + ROT pop dup push over -if L3 drop dup jump L4 L3: drop 0 L4: push UM* pop - D+ ROT pop UM* SWAP push 0 D+ pop push over pop SWAP Q.TO-INT push Q.TO-INT pop SWAP` (`SWAP` and `ROT` in line) — `floor(a*b / 2^16)`, signed (D-8), cut to two cells. Cells 0–2 of the product come from the unsigned cell products `a1*b1` (low cell), `a0*b1`, `a1*b0`, `a0*b0`, in that order, each input dropped after its last use and `b0` waiting on the return stack. Reading `a1` and `b1` as signed takes `(b1<0 ? a0 : 0)` and `(a1<0 ? b0 : 0)` off cell 2; each is folded in as soon as its operands are adjacent. The result is cells 0–2 shifted right 16 with `Q.TO-INT` twice. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference, and against v3's `q48_mul` for non-negative operands. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A`. |
|
||||
| `Q./` | CAP | `over over OR if ZERO drop push over pop SWAP over xor (Q/) 4 + b! !b -if L1 DNEGATE L1: push push -if L2 DNEGATE L2: pop pop dup if CHK drop jump DIV CHK: drop over -131072 and if CHK2 drop jump DIV CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT over over xor -if SAME drop drop -if NOOV jump OV SAME: drop push inv pop + inv -if OV NOOV: drop pop pop jump DIV OV: drop drop drop pop pop drop drop (Q/) 4 + b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; DIV: (UQ/) (Q/) 4 + b! @b -if L3 drop DNEGATE ; L3: drop ; ZERO: drop drop drop NODE-ERROR b! -1 !b over over OR if Z0 drop -if ZP drop drop 0 MSB ; ZP: drop drop -1 MAXHI ; Z0: drop ;` — D-11: `a * 2^16 / b`, rounded toward zero; saturated to Q max / Q min on overflow; on division by zero, Q max / Q min by the dividend's sign (0 for `0 / 0`) and `NODE-ERROR` set. The sign of the result waits in `(Q/)`. Overflow needs no division: the quotient reaches `2^(2N-1)` exactly when `|a| * 2^16 >= |b| * 2^(2N-1)`, possible only for `|b| < 2^17`, where it is `a1 >= b0 << (N-17)` unsigned. `MSB` and `MAXHI` are the high cells of Q min and Q max. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference (including `NODE-ERROR`), on width-native edge values, and against v3's `q48_div` for non-negative `a < 2^48`. Leaves its caller 3 data cells under its arguments and 2 return entries. Clobbers `A` and `B`. |
|
||||
| `(UQ/)` | CAP | Internal to `Q./`: unsigned `floor(a * 2^16 / b)` for `b != 0` and no overflow. Restoring division, `2N+16` steps, on one shifting register — quotient (starting as `a`) above remainder — with the quotient and divisor in `(Q/)` so the stacks carry only the remainder, the quotient bit and the loop count. Each step's quotient bit enters at the next step's shift; with no overflow the bits leaving the quotient in the last 16 steps are zeros. Trial subtraction by in-line sign tests (`x - y` with `y` on top is `push inv pop + inv`). Executed on the golden model (2026-10-02). Clobbers `A` and `B`. |
|
||||
| `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). `ROT dup -if L1 drop 1 jump L2 L1: drop 0 L2: push 2* + SWAP dup -if L3 drop 1 jump L4 L3: drop 0 L4: push 2* pop pop ROT + SWAP`, `ROT` and `SWAP` in line. Executed on the golden model (2026-10-02). |
|
||||
| `Q./` | CAP | `over over OR if ZERO drop push over pop SWAP over xor (Q/) 4 + b! !b -if L1 DNEGATE L1: push push -if L2 DNEGATE L2: pop pop dup if CHK drop jump DIV CHK: drop over -131072 and if CHK2 drop jump DIV CHK2: drop push push dup pop dup push N-18 FOR 2* UNEXT over over xor -if SAME drop drop -if NOOV jump OV SAME: drop push inv pop + inv -if OV NOOV: drop pop pop jump DIV OV: drop drop drop pop pop drop drop (Q/) 4 + b! @b -if OVP drop 0 MSB ; OVP: drop -1 MAXHI ; DIV: (UQ/) (Q/) 4 + b! @b -if L3 drop DNEGATE ; L3: drop ; ZERO: drop drop drop NODE-ERROR b! -1 !b over over OR if Z0 drop -if ZP drop drop 0 MSB ; ZP: drop drop -1 MAXHI ; Z0: drop ;` — D-11: `a * 2^16 / b`, rounded toward zero; saturated to Q max / Q min on overflow; on division by zero, Q max / Q min by the dividend's sign (0 for `0 / 0`) and `NODE-ERROR` set. The sign of the result waits in `(Q/)`. Overflow needs no division: the quotient reaches `2^(2N-1)` exactly when `|a| * 2^16 >= |b| * 2^(2N-1)`, possible only for `|b| < 2^17`, where it is `a1 >= b0 << (N-17)` unsigned. `MSB` and `MAXHI` are the high cells of Q min and Q max. Executed on the golden model (2026-10-02) against an independent limb-by-limb reference (including `NODE-ERROR`), on width-native edge values, and against v3's `q48_div` for non-negative `a < 2^48`. Leaves its caller 3 data cells under its arguments and 3 return entries. Clobbers `A` and `B`. |
|
||||
| `(UQ/)` | CAP | Internal to `Q./`: unsigned `floor(a * 2^16 / b)` for `b != 0` and no overflow. Restoring division, `2N+16` steps, on one shifting register — quotient (starting as `a`) above remainder — with the quotient and divisor in `(Q/)` so the stacks carry only the remainder, the quotient bit and the loop count. Each step's quotient bit enters at the next step's shift; with no overflow the bits leaving the quotient in the last 16 steps are zeros. Trial subtraction by in-line sign tests (`x - y` with `y` on top is `push inv pop + inv`), including the full double subtraction with its borrow, so the loop calls nothing but `D2*C`. Executed on the golden model (2026-10-02). Clobbers `A` and `B`. |
|
||||
| `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.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.LOG` `Q.SQRT` `Q.SIN` `Q.COS` | CAP | Algorithms ported from `q48_words.c`; the hosted C versions are the golden model. |
|
||||
| `Q.EXP` | CAP | v3's `q48_exp_approx`: `1 + x + x^2/2! + …` to 10 terms on `x = |q|`, stopping once a term is below 50 ulp; `1.0 Q./ e^|q|` for `q < 0`; `e^0 = 1.0`; `|q| >= 16.0` gives 0 (`q < 0`) or **Q max** (v3 returned all ones, which reads as −ulp under D-8). `x`, the term, the sum, the sign and the term index live in the variable `(QE)`, so only `Q.*` and `Q./` arguments sit on the stacks. Executed on the golden model (2026-10-02): bit for bit v3's result for `|q| < 16.0` (at 64-bit cells as the same value, high cell 0). Leaves its caller 3 data cells under its argument and 2 return entries. Clobbers `A` and `B`. |
|
||||
| `(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. |
|
||||
|
||||
Reference in New Issue
Block a user