POST coverage cluster 2/4: Q48.16 math primitives (proof-covered, previously untested)

New module (q48_words_test.c, Module 25 -- matches word_registry.c's own
existing numbering for this file's registration) covers all 23 words in
q48_words.c: no test file existed for this file at all before. Standard
WordTestSuite/TestCase tabular format, unlike ACL's hand-rolled style --
these are pure stateless functions, a natural fit. 28 TestCase entries;
values built via Q.FROM-INT/Q.1/Q.0, read back via Q.TO-INT for readable
log output.

Verified q48_16.h's q48_to_u64() sign-extends through a signed int64_t
intermediate before writing the Q.NEG/Q.ABS tests, rather than assuming
negative round-trip works.

Boot-verified: zero build warnings, all 23 words pass individually,
FINAL TEST SUMMARY 1003->1031 total / 965->993 passed (+28 exactly),
0 failed, 0 errors. Noted (pre-existing, not fixed): print_module_summary()
is called with hardcoded (name,0,0,0,0) across every WordTestSuite module
in the tree, including this new one -- decorative, always zero; the real
counts live in each word's own per-suite line and the global summary.

Cluster 3 of 4 in the POST-coverage sequence (code sweeps -> HOL green ->
POST coverage, one proof-covered cluster at a time). Two clusters left:
inference-engine accessors, physics freeze/diagnostic. Full writeup in
FABRIC-2.md Section J.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-18 23:30:46 -04:00
co-authored by Claude Sonnet 5
parent b2918fd0b2
commit 7100523656
7 changed files with 14601 additions and 1 deletions
+33
View File
@@ -1473,3 +1473,36 @@ evidence the bug was real and caught, not hidden), `logs/20260818-231403/` the f
Remaining clusters (Q48.16, inference-engine, physics freeze/diagnostic) not yet started —
next up, one at a time, same process: read the C implementation, write tests matching that
file's existing style, boot and verify before committing.
**Cluster 2, Q48.16 math primitives — done.** No test file for `q48_words.c` existed at all
(confirmed by absence, not by reading one and finding it thin) — all 23 registered words
(`Q.+`, `Q.-`, `Q.*`, `Q./`, `Q.ABS`, `Q.NEG`, `Q.LOG`, `Q.EXP`, `Q.SQRT`, `Q.SIN`, `Q.COS`,
`Q.FROM-INT`, `Q.TO-INT`, `Q.1`, `Q.0`, `Q.SCALE`, `Q.=`, `Q.<`, `Q.>`, `Q.0=`, `Q.MAX`,
`Q.MIN`, `Q.PRINT`) had zero POST coverage, not just the 17 `proof/COVERAGE.md` marks
proof-covered — covering the whole file was simpler and more valuable than partitioning by
proof status. Unlike ACL's hand-rolled style, these are pure stateless functions — a natural
fit for the standard `WordTestSuite`/`TestCase` tabular format the majority of the tree
already uses, so a new file (`q48_words_test.c`, Module 25 — matching `word_registry.c`'s own
existing "Module 25: Q48.16 Fixed-Point Math" numbering, not a coincidence, both count the
same registration) was added rather than extending an existing one. 28 `TestCase` entries
total (some words got two — a true/false pair for comparisons, a divide-by-zero edge case for
`Q./`). New words compose values via `Q.FROM-INT`/`Q.1`/`Q.0` and read them back via
`Q.TO-INT` so the log shows plain readable integers.
One genuine implementation subtlety checked before writing tests, not assumed: whether
`Q.TO-INT` round-trips a *negative* Q48.16 value correctly (needed for the `Q.NEG`/`Q.ABS`
tests). `q48_16.h`'s own `q48_to_u64()` comment confirms it shifts through a signed `int64_t`
intermediate specifically to sign-extend correctly — verified in the header before trusting
it, not discovered by a failing test.
Verified: zero build warnings, boot-tested, all 23 words individually pass (each printing its
own `N passed, 0 failed, 0 stubs, 0 errors` line). `FINAL TEST SUMMARY` total moved from
1003→1031, passed 965→993 (exactly +28, matching the new entry count precisely), failed and
errors unchanged at 0. Noted in passing, not fixed (pre-existing, not introduced by this
change): `print_module_summary()` is called with hardcoded `(name, 0, 0, 0, 0)` across
*every* `WordTestSuite`-based module in the tree, including this new one — the per-module
"X Summary: 0 passed, 0 failed..." line is decorative and always zero; the real counts are
only in each word's own line and the global `FINAL TEST SUMMARY`. Confirmed pre-existing by
checking `Arithmetic Words Summary` in the same boot log, not introduced here.
Two clusters left: inference-engine accessors (8 words), physics freeze/diagnostic (5 words).
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-19T03:14:02Z -->
<!-- Generated by mkcapsule --manifest 2026-08-19T03:28:49Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
+6
View File
@@ -103,6 +103,12 @@ void run_control_words_tests(VM * vm);
void run_starforth_words_tests(VM * vm);
void run_acl_words_tests(VM * vm);
/*
* @brief Run tests for Q48.16 fixed-point words (Module 25)
* @param vm Pointer to the VM instance
*/
void run_q48_words_tests(VM * vm);
/*
* @brief Run tests for Mama FORTH vocabulary (capsule system M7.1)
* @param vm Pointer to the VM instance
+230
View File
@@ -0,0 +1,230 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
This file is part of the StarForth project.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
express or implied, including but not limited to the warranties of
merchantability, fitness for a particular purpose, and noninfringement.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
* q48_words_test.c — POST tests for Q48.16 fixed-point words (Module 25)
*
* Proof-covered per proof/COVERAGE.md (StarForth_Q48_Words.thy, 17/23) but
* had no interpreter-level POST coverage at all before this file — no test
* file for q48_words.c existed. Values are built with Q.FROM-INT/Q.1/Q.0
* and read back with Q.TO-INT so results print as plain readable integers;
* pass/fail itself is judged only by vm->error (matching every other
* WordTestSuite-based module), so exact printed values are for a human
* reading the log, not machine-verified — same convention arithmetic_words_
* test.c already uses for its own "Should print: N" fields.
*/
#include "../include/test_runner.h"
#include "../include/test_common.h"
/* Q48.16 Words Test Suites - Module 25 */
static WordTestSuite q48_word_suites[] = {
{
"Q.+", {
{"basic", "1 Q.FROM-INT 2 Q.FROM-INT Q.+ Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.-", {
{"basic", "5 Q.FROM-INT 2 Q.FROM-INT Q.- Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.*", {
{"basic", "3 Q.FROM-INT 4 Q.FROM-INT Q.* Q.TO-INT . CR", "Should print: 12", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q./", {
{"basic", "10 Q.FROM-INT 2 Q.FROM-INT Q./ Q.TO-INT . CR", "Should print: 5", TEST_NORMAL, 0, 1, {0}},
{"by_zero", "5 Q.FROM-INT 0 Q.FROM-INT Q./ Q.TO-INT . CR", "Should print: 0 (division by zero yields q=0)", TEST_EDGE_CASE, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
2, {0}
},
{
"Q.ABS", {
{"basic", "3 Q.FROM-INT Q.NEG Q.ABS Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.NEG", {
{"basic", "5 Q.FROM-INT Q.NEG Q.TO-INT . CR", "Should print: -5", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.LOG", {
{"of_one", "Q.1 Q.LOG Q.TO-INT . CR", "Should print: 0 (ln 1.0 = 0)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.EXP", {
{"of_zero", "Q.0 Q.EXP Q.TO-INT . CR", "Should print: 1 (e^0 = 1)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.SQRT", {
{"of_four", "4 Q.FROM-INT Q.SQRT Q.TO-INT . CR", "Should print: 2", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.SIN", {
{"of_zero", "Q.0 Q.SIN Q.TO-INT . CR", "Should print: 0 (sin 0 = 0)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.COS", {
{"of_zero", "Q.0 Q.COS Q.TO-INT . CR", "Should print: 1 (cos 0 = 1)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.FROM-INT", {
{"round_trip", "7 Q.FROM-INT Q.TO-INT . CR", "Should print: 7", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.TO-INT", {
{"of_one", "Q.1 Q.TO-INT . CR", "Should print: 1", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.1", {
{"raw_value", "Q.1 . CR", "Should print: 65536", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.0", {
{"raw_value", "Q.0 . CR", "Should print: 0", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.SCALE", {
{"raw_value", "Q.SCALE . CR", "Should print: 65536", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.=", {
{"equal", "Q.1 Q.1 Q.= . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
{"not_equal", "Q.1 Q.0 Q.= . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
2, {0}
},
{
"Q.<", {
{"true", "Q.0 Q.1 Q.< . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
{"false", "Q.1 Q.0 Q.< . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
2, {0}
},
{
"Q.>", {
{"true", "Q.1 Q.0 Q.> . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
{"false", "Q.0 Q.1 Q.> . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
2, {0}
},
{
"Q.0=", {
{"true", "Q.0 Q.0= . CR", "Should print: -1 (true)", TEST_NORMAL, 0, 1, {0}},
{"false", "Q.1 Q.0= . CR", "Should print: 0 (false)", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
2, {0}
},
{
"Q.MAX", {
{"basic", "3 Q.FROM-INT 7 Q.FROM-INT Q.MAX Q.TO-INT . CR", "Should print: 7", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.MIN", {
{"basic", "3 Q.FROM-INT 7 Q.FROM-INT Q.MIN Q.TO-INT . CR", "Should print: 3", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{
"Q.PRINT", {
{"of_one", "Q.1 Q.PRINT CR", "Should print: 1.00000", TEST_NORMAL, 0, 1, {0}},
{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}
},
1, {0}
},
{NULL, {{NULL, NULL, NULL, TEST_NORMAL, 0, 0, {0}}}, 0, {0}}
};
/**
* @brief Executes all Q48.16 fixed-point word test suites
* @param vm Pointer to the Forth virtual machine instance
* @details Covers all 23 words registered by register_q48_words()
* (q48_words.c): arithmetic (+, -, *, /), sign (ABS, NEG),
* transcendental approximations (LOG, EXP, SQRT, SIN, COS),
* conversions (FROM-INT, TO-INT), constants (1, 0, SCALE),
* comparisons (=, <, >, 0=, MAX, MIN), and PRINT.
*/
void run_q48_words_tests(VM *vm) {
log_message(LOG_INFO, "Running Q48.16 Words Tests (Module 25)...");
WordContract mod = {CONTRACT_PHYSICS_TRANSPARENT, 0};
for (int i = 0; q48_word_suites[i].word_name != NULL; i++) {
log_message(LOG_TEST, "▶ Testing module: %s", __FILE__);
run_test_suite_m(vm, &q48_word_suites[i], mod);
}
print_module_summary("Q48.16 Words", 0, 0, 0, 0);
}
+1
View File
@@ -130,6 +130,7 @@ static TestModule test_modules[] = {
{"StarForth Words", NULL, 0, run_starforth_words_tests}, /* Module 22 */
{"ACL Words", NULL, 0, run_acl_words_tests}, /* Module 23: Word-Level ACL System */
{"Mama FORTH Words", NULL, 0, run_mama_forth_words_tests}, /* Module 24: Capsule System M7.1 */
{"Q48.16 Words", NULL, 0, run_q48_words_tests}, /* Module 25: Q48.16 Fixed-Point Arithmetic */
{NULL, NULL, 0, NULL} /* End marker */
};