number: Float32 arithmetic, comparison, text and narrowing from Float64 (slice 1) #35

Closed
opened 2026-10-05 21:07:12 +00:00 by orion · 7 comments
Collaborator

Context

Float32 is declared (std/number) but no compiled program can compute with it: the C back end lists its ten members (add, subtract, multiply, divide, remainder, negate, absolute, equals, compare, show) and Float32.tryFrom(String) as planned, there is no conversion from a Float64 down to a Float32, and Real cannot be implemented for it (LINEAR.md, "Open" item 8). What exists today: float as the C type of a 32-bit float (backend/c/type.trb), torb_remainder_f32, torb_equal_f32 and torb_compare_f32 in the runtime, TORB_KIND_F32 in the VM (runtime/machine.c, rounds to float after each operation), and Float64.from(Float32) as an intrinsic. Missing: the intrinsic rows, torb_show_f32, torb_parse_f32, the narrowing conversion, and the constant evaluator, which folds every float in 64 bits whatever its type (double rounding for a Float32 constant).

This is slice 1 of the Float32 work; the bit patterns are the follow-up slice and the migration of std/binary the last one (see Dependencies).

Source

Acceptance criteria

  • Float32 + - * / %, unary -, absolute(), ==, <, compare (total order, nan above everything like Float64.compare) and Show run in the C back end and in the VM; every operation rounds to binary32. The ten rows in backend/c/natives.trb are ready, none planned.
  • Float32.tryFrom(String) parses and "{value}" prints the shortest decimal that parses back to the same Float32 (torb_parse_f32, torb_show_f32; notation as for Float64).
  • Float32 with TryFrom<Float64, NumberRangeError> (the record's choice): rounds to nearest even, passes nan and the infinities through, fails with a NumberRangeError only for a finite value that would round to an infinity. Float64.from(Float32) stays exact.
  • A constant expression of type Float32 folds to the same bits the run time computes (const a: Float32 = 0.1 + 0.2 equals the same sum on variables); a case where this cannot be guaranteed is not folded.
  • Conformance programs in tests/conformance/ run identically in both back ends (extend floats.trb's Float32 line that says "no back end Show yet"): arithmetic, rounding (a sum that differs between 32 and 64 bits), overflow to infinity, subnormals, signed zero, nan comparisons, compare, parse/show round trip, the narrowing conversion at the edges.
  • Docs: docs/design/LINEAR.md item 8 updated, the Float32 paragraph of std/number's doc comments, docs/ROADMAP.md's planned-natives line, skills/ regenerated.

Design record point

LINEAR.md "Open" item 8 (the order of work and the conversion's shape); BINARY.md section 10 for what it unlocks. Real for Float32 (needs squareRoot and the <math.h> family in f32) stays out of this slice.

Seed impact

Yes. New natives (Float32.tryFrom(Float64), torb_show_f32, torb_parse_f32) and ready intrinsic rows: land as one or two Conventional Commits (teach, then docs/tests); nothing in std migrates onto them in this slice, so the seed refresh (Torben's) is only needed before the std/binary slice.

Dependencies

None. Blocks the Float64.bits()/Float32.bits() slice.

Moved from the planning lists of the docs; the design record stays the specification.

## Context `Float32` is declared (`std/number`) but no compiled program can compute with it: the C back end lists its ten members (`add`, `subtract`, `multiply`, `divide`, `remainder`, `negate`, `absolute`, `equals`, `compare`, `show`) and `Float32.tryFrom(String)` as planned, there is no conversion from a `Float64` down to a `Float32`, and `Real` cannot be implemented for it (LINEAR.md, "Open" item 8). What exists today: `float` as the C type of a 32-bit float (`backend/c/type.trb`), `torb_remainder_f32`, `torb_equal_f32` and `torb_compare_f32` in the runtime, `TORB_KIND_F32` in the VM (`runtime/machine.c`, rounds to `float` after each operation), and `Float64.from(Float32)` as an intrinsic. Missing: the intrinsic rows, `torb_show_f32`, `torb_parse_f32`, the narrowing conversion, and the constant evaluator, which folds every float in 64 bits whatever its type (double rounding for a `Float32` constant). This is slice 1 of the `Float32` work; the bit patterns are the follow-up slice and the migration of `std/binary` the last one (see Dependencies). ## Source - [ROADMAP.md, planned natives](https://git.torb.dev/torbscript/language/blob/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/ROADMAP.md#L109) - [LINEAR.md, "Open" item 8](https://git.torb.dev/torbscript/language/blob/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/LINEAR.md#L444) names the order: a `Float32 with TryFrom<Float64, NumberRangeError>` native, then arithmetic in the back ends - [BINARY.md section 10](https://git.torb.dev/torbscript/language/blob/393ef2608e4a60b8ff76d8b6b408951b822d887d/docs/design/BINARY.md#L237) ## Acceptance criteria - [ ] `Float32` `+ - * / %`, unary `-`, `absolute()`, `==`, `<`, `compare` (total order, `nan` above everything like `Float64.compare`) and `Show` run in the C back end and in the VM; every operation rounds to binary32. The ten rows in `backend/c/natives.trb` are ready, none planned. - [ ] `Float32.tryFrom(String)` parses and `"{value}"` prints the shortest decimal that parses back to the same `Float32` (`torb_parse_f32`, `torb_show_f32`; notation as for `Float64`). - [ ] `Float32 with TryFrom<Float64, NumberRangeError>` (the record's choice): rounds to nearest even, passes `nan` and the infinities through, fails with a `NumberRangeError` only for a finite value that would round to an infinity. `Float64.from(Float32)` stays exact. - [ ] A constant expression of type `Float32` folds to the same bits the run time computes (`const a: Float32 = 0.1 + 0.2` equals the same sum on variables); a case where this cannot be guaranteed is not folded. - [ ] Conformance programs in `tests/conformance/` run identically in both back ends (extend `floats.trb`'s `Float32` line that says "no back end `Show` yet"): arithmetic, rounding (a sum that differs between 32 and 64 bits), overflow to infinity, subnormals, signed zero, `nan` comparisons, `compare`, parse/show round trip, the narrowing conversion at the edges. - [ ] Docs: `docs/design/LINEAR.md` item 8 updated, the `Float32` paragraph of `std/number`'s doc comments, `docs/ROADMAP.md`'s planned-natives line, `skills/` regenerated. ## Design record point LINEAR.md "Open" item 8 (the order of work and the conversion's shape); BINARY.md section 10 for what it unlocks. `Real` for `Float32` (needs `squareRoot` and the `<math.h>` family in `f32`) stays out of this slice. ## Seed impact Yes. New natives (`Float32.tryFrom(Float64)`, `torb_show_f32`, `torb_parse_f32`) and ready intrinsic rows: land as one or two Conventional Commits (teach, then docs/tests); nothing in `std` migrates onto them in this slice, so the seed refresh (Torben's) is only needed before the `std/binary` slice. ## Dependencies None. Blocks the `Float64.bits()`/`Float32.bits()` slice. _Moved from the planning lists of the docs; the design record stays the specification._
Author
Collaborator

[loop:torbscript] intake: in scope (author orion, label loop). Float32 arithmetic and float bit patterns (ROADMAP planned natives, BINARY.md section 10). Touches natives, checker and both back ends (opus/high worker), likely the seed; NaN payload and rounding semantics may need a design-record entry, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)

[loop:torbscript] intake: in scope (author orion, label loop). `Float32` arithmetic and float bit patterns (ROADMAP planned natives, BINARY.md section 10). Touches natives, checker and both back ends (opus/high worker), likely the seed; NaN payload and rounding semantics may need a design-record entry, to settle in refine. Release freeze for 0.1.0 (2026-10-06) holds new PRs, next: refine (queued)
Author
Collaborator

[loop:torbscript] refine: still queued behind the intake order. next: waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z

[loop:torbscript] refine: still queued behind the intake order. next: waiting (ready buffer full: #4, #163, #164, #223 are ready) until 2026-10-10T04:00:00Z
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261012 until 2026-10-12T23:05Z

[loop:torbscript] claim refine by session:loop-torbscript-20261012 until 2026-10-12T23:05Z
orion changed title from number: Float32 arithmetic and the bit patterns of floats to number: Float32 arithmetic, comparison, text and narrowing from Float64 (slice 1) 2026-10-12 21:07:58 +00:00
Author
Collaborator

[loop:torbscript] decided by the loop: (1) Slice the work into three issues: #35 (arithmetic, comparison, text, narrowing), #348 (bits()/ofBits(_) on both float types, blocked by #35), #349 (migrate std/binary, blocked by #348 and by the seed refresh); each fits one PR of one or two commits, only #35 is ready. Why: the two-commit seed rule forces the migration after a refresh, and the natives are independent of the arithmetic. (2) Narrowing is Float32 with TryFrom<Float64, NumberRangeError> as LINEAR.md item 8 specifies, rounding to nearest even, passing nan/infinities through, failing only for a finite value that rounds to an infinity; no new name invented. (3) Float32 constants must fold to the run-time bits: measured in the code, checker/constant.trb folds every float in 64 bits whatever its type, which would double-round a Float32 constant, so it is an acceptance criterion. (4) Existing pieces measured by reading main: float C type, torb_remainder_f32/equal_f32/compare_f32, TORB_KIND_F32 in the VM and Float64.from(Float32) exist; show/parse for f32 and the ten intrinsic rows do not. (5) Real for Float32 stays out of scope (needs <math.h> f32 family). No owner question: all of this is internal and technical, and the float32() return type change was planned by BINARY.md section 10 before 1.0.

[loop:torbscript] decided by the loop: (1) Slice the work into three issues: #35 (arithmetic, comparison, text, narrowing), #348 (`bits()`/`ofBits(_)` on both float types, blocked by #35), #349 (migrate `std/binary`, blocked by #348 and by the seed refresh); each fits one PR of one or two commits, only #35 is `ready`. Why: the two-commit seed rule forces the migration after a refresh, and the natives are independent of the arithmetic. (2) Narrowing is `Float32 with TryFrom<Float64, NumberRangeError>` as LINEAR.md item 8 specifies, rounding to nearest even, passing `nan`/infinities through, failing only for a finite value that rounds to an infinity; no new name invented. (3) `Float32` constants must fold to the run-time bits: measured in the code, `checker/constant.trb` folds every float in 64 bits whatever its type, which would double-round a `Float32` constant, so it is an acceptance criterion. (4) Existing pieces measured by reading main: `float` C type, `torb_remainder_f32`/`equal_f32`/`compare_f32`, `TORB_KIND_F32` in the VM and `Float64.from(Float32)` exist; `show`/`parse` for f32 and the ten intrinsic rows do not. (5) `Real` for `Float32` stays out of scope (needs `<math.h>` f32 family). No owner question: all of this is internal and technical, and the `float32()` return type change was planned by BINARY.md section 10 before 1.0.
Author
Collaborator

[loop:torbscript] refine: rescoped to slice 1 (title and body updated: testable acceptance criteria, design record point LINEAR.md item 8, seed impact yes), follow-ups #348 and #349 created and chained; label ready set, next: implement (opus/high worker, touches checker constant folding, C back end, runtime and VM). Refine worker usage: roughly 100k tokens.

[loop:torbscript] refine: rescoped to slice 1 (title and body updated: testable acceptance criteria, design record point LINEAR.md item 8, seed impact yes), follow-ups #348 and #349 created and chained; label ready set, next: implement (opus/high worker, touches checker constant folding, C back end, runtime and VM). Refine worker usage: roughly 100k tokens.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261012 until 2026-10-13T03:10Z

[loop:torbscript] claim implement by session:loop-torbscript-20261012 until 2026-10-13T03:10Z
Author
Collaborator

[loop:torbscript] implement: PR #352 open, CI green, next: review

One commit (feat(runtime,backend), new natives only, nothing in std or the compiler uses them). Float32 arithmetic, ==, <, compare, Show, tryFrom(String) and tryFrom(Float64) in both back ends, constant folding rounded to binary32, new conformance program float32.trb, runtime C tests, docs and skills updated. Not changed: the checker's own constant evaluator still folds a Float32 in Float64 (diagnostics only).

Tokens: about 0.5M counted after a context compaction; the part before it is not metered.

[loop:torbscript] implement: PR #352 open, CI green, next: review One commit (`feat(runtime,backend)`, new natives only, nothing in std or the compiler uses them). Float32 arithmetic, `==`, `<`, `compare`, `Show`, `tryFrom(String)` and `tryFrom(Float64)` in both back ends, constant folding rounded to binary32, new conformance program `float32.trb`, runtime C tests, docs and skills updated. Not changed: the checker's own constant evaluator still folds a Float32 in Float64 (diagnostics only). Tokens: about 0.5M counted after a context compaction; the part before it is not metered.
argus closed this issue 2026-10-12 22:54:52 +00:00
Sign in to join this conversation.
No labels
loop
ready
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
torbscript/language#35
No description provided.