Skip to content

test(kernel): cover the 32-bit rounding path and round_py vs CPython - #41

Merged
dandwhelan merged 3 commits into
mainfrom
ci/kernel-coverage
Aug 15, 2026
Merged

dandwhelan merged 3 commits into
mainfrom
ci/kernel-coverage

Conversation

@dandwhelan

Copy link
Copy Markdown
Owner

Summary

Three gaps in the kernel's CI coverage, all exposed while doing the round_py rewrite in #39. None of these are repairs to something broken — CI has been green throughout — they are coverage for code that currently ships untested.

1. The 32-bit arithmetic path was never executed

__int128 is a 64-bit-target extension, so the i686 and armv7l builds scale through uint64_t instead. But the runner is x86_64 and the cross-built 32-bit binaries are never run, so that path shipped with no coverage at all.

build_kernel.sh now honours EXTRA_CXXFLAGS, and CI builds a -DPK_NO_INT128 variant — which forces the narrow path on a 64-bit host — then puts it through the full parity suite.

2. The binaries that actually ship were the ones nobody verified

The ordering was wrong:

  1. pre-commit verifies the checked-in x86_64 binary — as it stood when the PR was opened, i.e. the previous one
  2. kernel-binaries regenerates all six and pushes them
  3. that push's run needs manual approval

So without intervention the regenerated binaries merge unverified. This happened on #39 and was only caught because I approved the pending run by hand. Verification now happens inside kernel-binaries, after the cross-build and before the commit.

x86_64 is the only one the runner can execute natively; the others rely on the source-level parity coverage in pre-commit (now including the narrow path, per #1).

3. round_py had no test

Parity was only ever checked end to end through the Python engine, over realistic SoC magnitudes. That is precisely why the old snprintf("%.*f") implementation shipped for so long with a 64-byte buffer: 1e300 needs 305 characters, so it truncated and strtod parsed a completely different number — round(1e300, 3) returned 1e62, and nothing noticed.

A pk_round_py_test hook exposes round_py so tests/test_round_py_parity.py can diff it bit-for-bit against CPython's round() over the inputs that break naive implementations: exact ties at j/2^p, nextafter neighbours either side of a decimal boundary, halfway values at 4dp, subnormals, and extreme magnitudes.

Verification

The new test demonstrably catches the original defect. Rebuilding the kernel with the pre-#39 round_py:

ERROR: round_py(1e+63, 3) [huge] returned 1e+62, CPython round() gives 1e+63
ERROR: round_py parity FAILED - 80 mismatches out of 19776 values

All 80 on the huge-value cases. Against the current implementation it passes — 19,776 values in 0.03s, on both the __int128 and narrow builds.

Also run locally: round_py_parity, kernel_parity (450 randomised configs) and model_kernel all pass.

🤖 Generated with Claude Code

dandwhelan and others added 3 commits August 15, 2026 14:26
Three gaps in the kernel's CI coverage, all exposed by the round_py rewrite
in #39.

The 32-bit arithmetic path was never executed. __int128 is a 64-bit-target
extension, so the i686 and armv7l builds scale through uint64_t instead, but
the runner is x86_64 and the cross-built 32-bit binaries are never run - so
that path shipped with no coverage at all. build_kernel.sh now honours
EXTRA_CXXFLAGS and CI builds a -DPK_NO_INT128 variant, which forces the narrow
path on a 64-bit host, then puts it through the full parity suite.

The binaries that actually ship were the ones nobody verified. The pre-commit
job checks the checked-in x86_64 binary as it stood when the PR was opened -
the previous one - and the kernel-binaries job then regenerates all six and
pushes them. That push's workflow run needs manual approval, so without it the
regenerated binaries merge unverified; this happened on #39 and was only caught
by approving the run by hand. Verification now happens inside kernel-binaries,
after the cross-build and before the commit.

round_py itself had no test. Parity was only ever checked end to end through
the Python engine over realistic SoC magnitudes, which is why the old
snprintf("%.*f") implementation shipped for so long with a 64-byte buffer:
1e300 needs 305 characters, so it truncated and strtod parsed a different
number - round(1e300, 3) returned 1e62 and nothing noticed. A pk_round_py_test
hook now exposes round_py so tests/test_round_py_parity.py can diff it
bit-for-bit against CPython round() over the inputs that break naive
implementations: exact ties at j/2^p, nextafter neighbours either side of a
decimal boundary, halfway values at 4dp, subnormals and extreme magnitudes.

Verified the new test actually catches the original defect: rebuilt the kernel
with the pre-#39 round_py and it reports 80 mismatches, all on the huge-value
cases, and passes on the current implementation. 19776 values checked in 0.03s,
on both the __int128 and narrow builds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
cspell applies a different dictionary per language, so ndigits passes in
prediction_kernel.cpp - the C++ dictionary knows it - but not in the new
Python test. CXXFLAGS is new to the repo entirely.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dandwhelan
dandwhelan merged commit c911bea into main Aug 15, 2026
2 checks passed
@dandwhelan
dandwhelan deleted the ci/kernel-coverage branch August 15, 2026 13:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant