decimal: decimal literals and constants for Decimal #335

Closed
opened 2026-10-12 13:15:57 +00:00 by orion · 6 comments
Collaborator

Part of #34, after #332 and the runtime issue #334.

Goal

const price: Decimal = 19.99 (and static zero: Self = 0.0 in std/number) must evaluate in both back ends. Today quote.trb and the IR have no static form for a Decimal: "there is no float literal type, so a float constant is not a LiteralValue" (compiler/src/ir/ir.trb). Give a Decimal constant a static form (the literal's exact text) that both back ends turn into torb_decimal_parse of that text at initialisation, so 19.99 is exact and 0.1 + 0.2 == 0.3 holds.

Acceptance criteria

  • A decimal or integer literal adapting to Decimal keeps every written digit (no pass through Float64); a literal with more digits than the range limit of #332 is a compile error naming the limit.
  • ir text format, bytecode and C back end handle the constant; plain.trb, exhaustive.trb and quote.trb need no special case left that returns "not supported" for Decimal.
  • Checker tests: literal adaptation, error for a too long literal, Decimal in a match on a literal pattern behaves as #332 says.
  • Conformance program decimal-literals identical in the VM and natively.

Seed impact

Probably none: no compiler source uses a Decimal literal. If the change adds a name looked up by string or a new IR form the seed must read, take the two-commit route (CLAUDE.md "Seed and breaking changes").

Size

One PR, feat(ir): ... / feat(checker): ....

Part of #34, after #332 and the runtime issue #334. ## Goal `const price: Decimal = 19.99` (and `static zero: Self = 0.0` in `std/number`) must evaluate in both back ends. Today `quote.trb` and the IR have no static form for a `Decimal`: "there is no float literal type, so a float constant is not a `LiteralValue`" (`compiler/src/ir/ir.trb`). Give a `Decimal` constant a static form (the literal's exact text) that both back ends turn into `torb_decimal_parse` of that text at initialisation, so `19.99` is exact and `0.1 + 0.2 == 0.3` holds. ## Acceptance criteria - [ ] A decimal or integer literal adapting to `Decimal` keeps every written digit (no pass through `Float64`); a literal with more digits than the range limit of #332 is a compile error naming the limit. - [ ] `ir` text format, bytecode and C back end handle the constant; `plain.trb`, `exhaustive.trb` and `quote.trb` need no special case left that returns "not supported" for `Decimal`. - [ ] Checker tests: literal adaptation, error for a too long literal, `Decimal` in a `match` on a literal pattern behaves as #332 says. - [ ] Conformance program `decimal-literals` identical in the VM and natively. ## Seed impact Probably none: no compiler source uses a `Decimal` literal. If the change adds a name looked up by string or a new IR form the seed must read, take the two-commit route (CLAUDE.md "Seed and breaking changes"). ## Size One PR, `feat(ir): ...` / `feat(checker): ...`.
Author
Collaborator

[loop:torbscript] refine: slice 3 of #34, next: waiting (dependency #334)

[loop:torbscript] refine: slice 3 of #34, next: waiting (dependency #334)
Author
Collaborator

[loop:torbscript] claim refine by session:loop-torbscript-20261022 until 2026-10-23T01:30:00Z

[loop:torbscript] claim refine by session:loop-torbscript-20261022 until 2026-10-23T01:30:00Z
Author
Collaborator

[loop:torbscript] decided by the loop: refined acceptance criteria, since #332 landed (docs/design/DECIMAL.md section 9 and 14 slice 3):

  1. No digit limit. The issue text speaks of "the range limit of #332", but the record sets none on digits or magnitude (section 2). The only compile error for a Decimal literal is a written scale that does not fit Int32 (section 9), e.g. 1e-3000000000. Why: the record is the source of truth, and the runtime stores digits in an arbitrary-size block.
  2. Static form is the unscaled digits (sign + decimal digits, hex/binary/octal integer literals already converted, _ dropped) plus an Int32 scale, never a Float64. Both back ends emit it as an immortal static block (TORB_IMMORTAL_COUNT) so a constant allocates nothing at run time; the checker/constant evaluator builds it. Why: record section 9/10. A new IR constant form is added next to LiteralValue-based statics (compiler/src/ir/ir.trb), text format, bytecode and C back end handle it.
  3. Decimal.zero/Decimal.one in std/number become 0 and 1 (scale 0), per section 9, so Decimal.zero + 5 keeps the scale of 5. -19.99 is the negation of 19.99, scale 2.
  4. match on a literal pattern compares by numeric value like == (section 3): the pattern 1.0 matches 1.00. A Decimal match is never exhaustive without a wildcard arm. Why: the record defines equality by value and does not special-case patterns; reusing == keeps one rule.
  5. Literals without an expected type stay Float64/Int64.
  6. Seed: no touch expected (no compiler source uses a Decimal literal); if ir --statistics . shows the compiler reaching a new name or IR form the seed must read, use the two-commit route.
[loop:torbscript] decided by the loop: refined acceptance criteria, since #332 landed (docs/design/DECIMAL.md section 9 and 14 slice 3): 1. **No digit limit.** The issue text speaks of "the range limit of #332", but the record sets none on digits or magnitude (section 2). The only compile error for a Decimal literal is a written scale that does not fit `Int32` (section 9), e.g. `1e-3000000000`. Why: the record is the source of truth, and the runtime stores digits in an arbitrary-size block. 2. **Static form** is the unscaled digits (sign + decimal digits, hex/binary/octal integer literals already converted, `_` dropped) plus an `Int32` scale, never a `Float64`. Both back ends emit it as an immortal static block (`TORB_IMMORTAL_COUNT`) so a constant allocates nothing at run time; the checker/constant evaluator builds it. Why: record section 9/10. A new IR constant form is added next to `LiteralValue`-based statics (`compiler/src/ir/ir.trb`), text format, bytecode and C back end handle it. 3. **`Decimal.zero`/`Decimal.one`** in `std/number` become `0` and `1` (scale 0), per section 9, so `Decimal.zero + 5` keeps the scale of `5`. `-19.99` is the negation of `19.99`, scale 2. 4. **`match` on a literal pattern** compares by numeric value like `==` (section 3): the pattern `1.0` matches `1.00`. A Decimal match is never exhaustive without a wildcard arm. Why: the record defines equality by value and does not special-case patterns; reusing `==` keeps one rule. 5. Literals without an expected type stay `Float64`/`Int64`. 6. **Seed:** no touch expected (no compiler source uses a Decimal literal); if `ir --statistics .` shows the compiler reaching a new name or IR form the seed must read, use the two-commit route.
Author
Collaborator

[loop:torbscript] refine: ready. Goal and the record point (DECIMAL.md section 9, slice 3) are clear; the acceptance criteria are corrected per the decided-by-the-loop comment above (no digit limit; compile error only for a scale outside Int32; match literal patterns by value; zero/one become scale 0; conformance program decimal-literals identical in VM and native). Seed: no touch expected. Size: one PR, feat(checker): ... / feat(ir): .... Tokens used by this refine worker: ~45k. next: implement

[loop:torbscript] refine: ready. Goal and the record point (DECIMAL.md section 9, slice 3) are clear; the acceptance criteria are corrected per the decided-by-the-loop comment above (no digit limit; compile error only for a scale outside Int32; match literal patterns by value; zero/one become scale 0; conformance program `decimal-literals` identical in VM and native). Seed: no touch expected. Size: one PR, `feat(checker): ...` / `feat(ir): ...`. Tokens used by this refine worker: ~45k. next: implement
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T04:17:00Z

[loop:torbscript] claim implement by session:loop-torbscript-20261023 until 2026-10-23T04:17:00Z
Author
Collaborator

[loop:torbscript] implement: PR #401 is open and CI is green (gates, tier A and B, conformance in native, VM and debugger lowering, bootstrap). Number literals now adapt to Decimal with every written digit (only a scale outside Int32 is a compile error); the IR has a new StaticContents.Decimal form emitted as an immortal block by the C back end and the bytecode (new kernel op 86, no native); Decimal.zero/one are 0/1; a match compares literals by numeric value. Checker tests plus the conformance program decimal-literals are in. Seed: no refresh needed (the "pack the seed" job was skipped). Fix rounds: 3 (pdisc stamp, format and declarations, expected output and allocation budget 124, one commit type). Token usage: not metered inside the subagent; the run spanned two context windows. next: review

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/401 is open and CI is green (gates, tier A and B, conformance in native, VM and debugger lowering, bootstrap). Number literals now adapt to `Decimal` with every written digit (only a scale outside `Int32` is a compile error); the IR has a new `StaticContents.Decimal` form emitted as an immortal block by the C back end and the bytecode (new kernel op 86, no native); `Decimal.zero`/`one` are `0`/`1`; a `match` compares literals by numeric value. Checker tests plus the conformance program `decimal-literals` are in. Seed: no refresh needed (the "pack the seed" job was skipped). Fix rounds: 3 (pdisc stamp, format and declarations, expected output and allocation budget 124, one commit type). Token usage: not metered inside the subagent; the run spanned two context windows. next: review
argus closed this issue 2026-10-23 05:39:45 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
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.

Dependencies

No dependencies set

Reference
torbscript/language#335
No description provided.