Skip to content

fix(base): convert Nat to F32 without narrowing to U32 - #1610

Open
oxura wants to merge 6 commits into
bendlang:mainfrom
oxura:fix/f32-from-nat-width-20261016
Open

oxura wants to merge 6 commits into
bendlang:mainfrom
oxura:fix/f32-from-nat-width-20261016

Conversation

@oxura

@oxura oxura commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1609.

F32.from_nat keeps the full Nat instead of narrowing through U32. It is a law with a direct intrinsic, like U32.to_f32; no host-only F32.read or NaN fallback.

Merged main aee1b1f. The cap blocker is gone: comp.ts is 65,989 / 66,000 tokens and repo-gate passes 54/54. The four cast rules share the existing tpl_ops; no caps changed or code moved out.

All five cases, both ordinary and inside !, pass on native CPU, Bun and Node. Four existing U32/Nat/float control programs also match their expected output on the interpreter, native CPU and compiled JS on both hosts. Native runs use the new +RTS option brackets.

I have not run Metal, CUDA or the private mini-cluster suite.

@Lorenzobattistela Lorenzobattistela left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked on main 2e9722d: the four new cases print True on the native binary, the .js build and inside a bang, and the full test suite passes. A few things to fix in the reference body:

  1. The new body only works because the intrinsic shadows it. F32.read is host-only: on device, f32_read posts ERR_FIDS (comp.ts:360). If the f32_from_nat lookup ever misses (a rename of F32.from_nat, a flag on the def), F32.from_nat inside a bang would fail at runtime on Metal/CUDA instead of returning a value. The old body was plain arithmetic and ran anywhere. Please keep the reference body free of host-only primitives: either split into word halves like F32.to_nat.wide does, or declare it as a law the way U32.to_f32 is.

  2. The F32.div(0.0, 0.0) fallback can't be reached: F32.read always accepts what Nat.show produces. It reads as if some Nat could become NaN, and if F32.read ever regressed it would hide the failure as a NaN. Please drop it along with the change above.

  3. The test can't see the body, since every lane runs the cast. Please add a call inside a bang (e.g. f!(…) over the same four inputs) so the device cast from a 64-bit Nat to f32 on Metal/CUDA is pinned too, plus one small value (e.g. 16777217 -> 16777216) for the old range.

@oxura

oxura commented Oct 22, 2026

Copy link
Copy Markdown
Contributor Author

Went with a law, like U32.to_f32, so there's no F32.read or NaN fallback left in the reference. Added all five cases inside !, including 16777217 → 16777216. All ten checks pass on native CPU, Bun, and Node.

Merged 2e9722d too. The compiler is still 32 tokens over the current cap, so I left this draft. No Metal/CUDA run on my side.

@oxura
oxura marked this pull request as ready for review October 23, 2026 16:55
@oxura

oxura commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

@Lorenzobattistela this is back under the cap: 65,989/66,000 after merging aee1b1f, repo-gate 54/54. Shared the existing cast templates; the full-width law and all ten checks stay. Fresh CPU, Bun and Node runs pass, plus the U32/Nat/float controls. Marked ready. No Metal/CUDA run from me.

@YidaWeng YidaWeng left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent verification on main 295e9965 plus this diff:

  • Issue reproducer: 0 before, 2^32 after. F32.bits(F32.from_nat(2^32)) is 1333788672 (0x4F800000) on native C, Bun and Node.
  • Boundary sweep (31 values around 2^24, 2^32 and 2^48-1): C, Bun and Node agree bit-for-bit.
  • tests/base 41/41 runnable tests pass on both the C and JS lanes; num_kit and the U32/Nat/F32 controls pass.
  • comp.ts is 65,989 / 66,000 tokens; bun gates/repo.ts is PASS: 54 / 54 with the new test tracked.
  • --checkup prints the ten expected Trues; -o .bendtt succeeds and the law goes out opaque with a valid model.
  • Note for the reproducer: F32.show prints 4294967300 for 2^32 (its 9-significant-digit round-trip form), so the issue's display is expected; the test's F32.is_eq is the right check.

Not run here: Metal/CUDA, the cluster gates/test.ts/gates/safe.ts, and the Lean kernel. The ! case is there for the M4 gate. LGTM pending that.

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.

F32.from_nat wraps values at 2^32

3 participants