# nb-23 — Numbers and the standard library: every figure the 0.2 machine proves All law in this series is fictional; every meter, tariff, bill and calendar is synthetic and unofficial. No real municipal deployment or legal-validity claims. Tool `law 0.1.0`, language version `0.2`, semantics `law.core/0.2`, std `0.2.0` (from `law --version`, quoted below). ## 1. Situation Mira, the Northbridge water clerk, closes the March billing. Three metered tanks hold 12, 9 and 15 cubic metres. A 2,500-litre rooftop tank trips the 2,000-litre inspection threshold, and a 900-litre one does not. A 2 km pipe run converts to 2,000 metres. The quarterly bill of 1,410 KZT divides by three months into exactly 470 KZT, while 1,000 KZT divided by three refuses loudly instead of rounding quietly. A 2 March meter reading plus ten calendar days falls due on 12 March, and three working days later is 5 March. Her auditor asks one question per number: *which of these did the pinned machine prove, which did it refuse — and is each refusal a property of our build or of the language itself?* This article answers with one green suite plus pinned refusal reproducers. Three ideas run through the answers. Number-like types ship in **numeric families** — exact rationals in one family, certified bounds in another, solvers and currency conversion in further ones — each versioned and supported together. Operators follow a **kind discipline**: each one names its operand kinds explicitly, so `Money / Decimal` is exact or loud while `Money / Integer` is rejected at check time. And certified rounding faces **certificate sufficiency**: does this interval sit inside a single rounding cell, or must the machine refuse to guess? Truth statuses work as in [nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/) (`TRUE_ONLY` = established, `NEITHER` = established neither way). ## 2. Prerequisites [nb-01: First permit: facts, a rule and a question](/tutorials/northbridge/nb-01-first-permit/): facts, strict rules, the four truth statuses, `law test` as the way to check a claim. [nb-04: The permit fee](/tutorials/northbridge/nb-04-permit-fee/): `Money` arithmetic and `HALF_UP` rounding — the everyday numbers this article completes. [nb-15: Quantities and dimensions](/tutorials/northbridge/nb-15-quantities/): `Quantity`, `Magnitude`, `units.si`, explicit conversion. [nb-16: Exact computation and proven bounds](/tutorials/northbridge/nb-16-exact-bounds/): exact rationals and derivation-carrying `Bounds`. [nb-17: Numeric families and support boundaries](/tutorials/northbridge/nb-17-numeric-families/): certified profiles and the version boundary — this article turns its map into one green suite plus pinned refusal reproducers. Each new function is explained where it first appears: `abs`, `pow`, `average` with its precision policy, `div_round` over `Money`, `percent`, `text_matches` as a conjunct, `add_business_days` / `add_calendar_period` / `add_legal_term` with their calendar, the monotone `Bounds` lift, the `math::` and `bounds::` short spellings, and `rounds_to` with its three arms. ## 3. Minimal example Excerpts are from `packs/examples/language-demo/numerics/package.law` (identifiers as written; unrelated rules cut). Standalones are complete probe files: save each pair and run the section 4 command against it. Excerpt 1 — exact kinds stay apart (lines 52–71). Mira's billing divides meter readings three ways. Watch the return types: three divisions, three result kinds, no silent conversion. ```law pure function half7() -> Rational = 7 / 2; pure function exact_half() -> Decimal = 7.0 / 2.0; pure function third() -> Rational = 10.0 / 3.0; ``` One idea: `7 / 2` is `Rational`, `7.0 / 2.0` is `Decimal` (its denominator is 10-smooth), and `10.0 / 3.0` is `Rational` again — the kind is computed from the operands, never wished by the author. Excerpt 2 — money divides exactly or loudly (lines 84–95). The quarterly bill splits three ways; a thousand KZT does not split at all. Watch the divisor kind in both lines: ```law pure function monthly() -> Money = 1410 KZT / 3.0; pure function baddiv() -> Money = 1000 KZT / 3.0; ``` One idea: the divisor is `Decimal`, never `Integer`. `1410 KZT / 3` with an integer divisor is rejected at check time (`LDC-E2108`), and the inexact quotient is a coded `INEXACT_DIVISION`, never a quietly rounded amount. Standalone A — full content of `probe.law`: the average that refuses to guess over integers. Mira's three tank readings average to exactly twelve — yet this rule fires nothing: ```law language "law.core" version "0.2"; package probe version "0.1.0"; namespace "urn:probe"; relation reading(k: Integer) kind empirical; relation avg_silent() kind institutional; rule AvgSilent strict { when average(collect all x: Integer where reading(x)) == 12; then avg_silent(); } ``` One idea: `average` over `Integer` has no rounding policy to apply, so the rule never fires. The same call over `Money` (with `2, "HALF_UP"`) or over `Decimal` does fire — the kind decides. Standalone B — full content of `probe.lawtest` for Standalone A (header plus one test, complete as shown). The three readings are asserted, and the test expects silence: ```law language "law.core" version "0.2"; package probe version "0.1.0"; namespace "urn:probe"; test "average over integers stays silent" { given { context { legal_time @2026-03-02; decision_time @2026-03-02T09:00:00Z; knowledge_time @2026-03-02T09:00:00Z; timezone "UTC"; } assert "r12": reading(12) { origin case_input; } assert "r9": reading(9) { origin case_input; } assert "r15": reading(15) { origin case_input; } } evaluate truth(avg_silent()); expect truth_status == NEITHER; } ``` One idea: silence here is specified behaviour, not missing data. Compare section 6, where the same readings feed `sum`, `min`, `max` and `count` — and every one of those fires. The suite's real counterparts live in [numerics/package.law](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/package.law) (`AvgSilent`, `AvgBill` over `Money`, `AvgSample` over `Decimal`) and [tests/numerics.lawtest](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/tests/numerics.lawtest). The calendar, policy and source the date terms need are lines 14–49 of the same package file; the refusal reproducers are [evidence/snippets/](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/evidence/snippets/). ## 4. Command and result Pin the machine first: five facts — tool, language, semantics, std, binary. Run from the repository root: ```sh law --version ``` Observed: ```text law 0.1.0 семантика: law.core/0.2 std для языка 0.2: 0.2.0 хэш бинаря: sha256:78dea06ce928547e87bd9875a2556cc663def37cbb78c8b04aa0da57839c95d7 ``` The Russian lines name the semantics (`семантика: law.core/0.2`), the std version for language 0.2 (`std для языка 0.2: 0.2.0`) and the binary hash (`хэш бинаря`). These are separate facts: a newer tool could keep the language version and still change what executes. Now the whole executable surface in one run: ```sh law test packs/examples/language-demo/numerics ``` Observed (last lines; engine `law 0.1.0`): ```text ok [demo.northbridge.numerics] tests/numerics.lawtest / certified root rounds to three ok [demo.northbridge.numerics] tests/numerics.lawtest / formula agrees on three ok [demo.northbridge.numerics] tests/numerics.lawtest / wrong cell stays silent ok [demo.northbridge.numerics] tests/numerics.lawtest / wide interval is loud итого: 67 проверено, 67 прошли, 0 не прошли, 0 не исполнены; код 0 ``` All 67 pass — the total reads "67 проверено, 67 прошли, 0 не прошли, 0 не исполнены; код 0": 67 checked, 67 passed, 0 failed, 0 unexecuted, exit code 0. The last lines show the sufficiency verdicts: the certified root publishes its cell, the wrong cell stays silent, and the wide interval is loud. The package checks clean and its imports are canonical: ```sh law engine check packs/examples/language-demo/numerics law fix imports packs/examples/language-demo/numerics ``` Observed: `check OK: packs/examples/language-demo/numerics` and `блоки 'use self' канонические` — the `use self` blocks are canonical. The package compiles and its imports need no repair. Next, the standalone average probe from section 3. Save Standalones A and B as `/tmp/nb23-probe/probe.law` and `/tmp/nb23-probe/probe.lawtest`, then run: ```sh law engine test /tmp/nb23-probe/probe.lawtest --program /tmp/nb23-probe/probe.law ``` Observed: `test PASS: average over integers stays silent`. The pass means the rule stayed silent exactly as the test expects — the machine refused the quiet integer mean. One refusal pin, for the record — exact currency conversion. Mira cannot convert bills at an exact rate in this build: ```sh law engine check packs/examples/language-demo/numerics/evidence/snippets/fx02.law.txt ``` Observed, in full: ```text packs/examples/language-demo/numerics/evidence/snippets/fx02.law.txt:7:10: error LDC-E2403: COMPUTABILITY_NOT_PROVED (LDC-E2403): function "f" не доказана как PURE_DETERMINISTIC: вызов "fx_convert" не имеет доступного effect descriptor (imported/unknown dependency) (§46/§47.5) packs/examples/language-demo/numerics/evidence/snippets/fx02.law.txt:7:39: error LDC-E2105: функция "fx_convert" не объявлена (ни в файле, ни в std v1-списке) ``` All ten snippets refuse the same two-layer way (terms) or with `LDC-E2101` (the `Number`, `Algebraic`, `RealExpr` types); each file says so in its header comment. Two layers, both static — nothing executes. `LDC-E2105` says the name is declared neither in the file nor in the std v1-list, the executed 0.2 slice. `LDC-E2403` says that without an effect descriptor for the callee, the enclosing function cannot be proved `PURE_DETERMINISTIC` ([§46](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#46-functions)/[§47.5](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#475-computability-classification)). Two pins below, one per profile family, in full — first the RealExpr constant `ln2()` (profile 0.5, DECISION-0438 project). Tool `law 0.1.0`, semantics `law.core/0.2`, std `0.2.0`, same binary as above. Standalone C — full content of `boundaries/evidence/snippets/ln02.law.txt`: the `ln2` probe. The `Rational` return type isolates the *term* refusal from the separate `RealExpr`-type refusal below: ```law language "law.core" version "0.2"; package probe version "0.1.0"; namespace "urn:probe"; function f() -> Rational = ln2(); ``` ```sh law engine check packs/examples/language-demo/boundaries/evidence/snippets/ln02.law.txt ``` Observed, in full (exit 1): ```text packs/examples/language-demo/boundaries/evidence/snippets/ln02.law.txt:4:10: error LDC-E2403: COMPUTABILITY_NOT_PROVED (LDC-E2403): function "f" не доказана как PURE_DETERMINISTIC: вызов "ln2" не имеет доступного effect descriptor (imported/unknown dependency) (§46/§47.5) packs/examples/language-demo/boundaries/evidence/snippets/ln02.law.txt:4:28: error LDC-E2105: функция "ln2" не объявлена (ни в файле, ни в std v1-списке) ``` The same two layers as the currency pin: name resolution, then computability proof. The Russian text says the function "is not proved PURE_DETERMINISTIC" and "is not declared (neither in the file nor in the std v1-list)". Second, exact angle conversion (profile 0.7, DECISION-0440 project, versioned policy `units/0.7`): Standalone D — full content of `boundaries/evidence/snippets/angle02.law.txt`: ```law language "law.core" version "0.2"; package probe version "0.1.0"; namespace "urn:probe"; function f() -> Rational = angle_convert(90, "deg", "rad", "units/0.7"); ``` ```sh law engine check packs/examples/language-demo/boundaries/evidence/snippets/angle02.law.txt ``` Observed, in full (exit 1): ```text packs/examples/language-demo/boundaries/evidence/snippets/angle02.law.txt:4:10: error LDC-E2403: COMPUTABILITY_NOT_PROVED (LDC-E2403): function "f" не доказана как PURE_DETERMINISTIC: вызов "angle_convert" не имеет доступного effect descriptor (imported/unknown dependency) (§46/§47.5) packs/examples/language-demo/boundaries/evidence/snippets/angle02.law.txt:4:28: error LDC-E2105: функция "angle_convert" не объявлена (ни в файле, ни в std v1-списке) ``` Same two codes: naming the `units/0.7` policy in the call does not conjure an implementation — the term is still outside the executed slice. Both refusals are facts about this build and this profile only. The names are specified (`law.std.data` [§257](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#257-lawstddata) export list; `RealExpr` nullary terms slice 0.5; exact units [§49.3](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/08-part-viii-type-system.ru.md#493-точные-единицы-под-версионированной-политикой-decision-0440) slice 0.7) and catalogued (registry symbols with profiles `0.5`/`0.7`, `experimental`), but this build executes the 0.2 line only. No version promise follows: whether any future tool executes profiles 0.5/0.7 is an open obligation recorded in section 8, not a claim these pins can verify. ## 5. Why this construct Mira needs to know the usable surface without guessing. In 0.2, `Integer`, `Natural`, `Decimal`, `Rational`, `Money`, `Quantity`, `Magnitude`, `Bounds`, `Text`, dates and instants all execute — and so do `abs`, `pow`, `sum`, `min`, `max`, `count`, `average` (over `Money`/`Decimal`), `round`, `div_round`, `percent`, `text_length`, `convert`, `with_unit`, `magnitude`, `unit_convert`, `scalar_of`, `quantity_of`, `unit_exponent`, every date/time span and bound, `add_duration`, the three calendar steps, all six certified leaves, all five bounds compositions, `round_bounds` and `rounds_to`: 67 of 67 in the suite above. The kind discipline keeps inexactness loud. `1410 KZT / 3.0` carries the currency into `470 KZT`; `1000 KZT / 3.0` carries an `INEXACT_DIVISION` issue the test pins by name; `470 KZT + 10 EUR` stays `NEITHER` — mixed currencies never add. The `Integer` divisor is refused even earlier, at check time. The average silence refuses quiet integer means. `average` over `{12, 9, 15}` as `Integer` fires nothing, while `sum`, `min`, `max` and `count` over the same readings fire all four. The norm must name its rounding (`2, "HALF_UP"`) and a roundable kind first. The calendar separates three different date computations. Three working days from 2 March land 5 March (the pinned table is consulted). One calendar week and seven calendar days both land 10 March (the SPEC [§84.2](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/12-part-xii-events-actions-temporal-model.ru.md#842-calendarperiod) pair, no table involved). One calendar month lands 3 April, not 2 April — `start_count = next_day` shifts the step, and the suite pins the shifted date, not the naive guess. Ten calendar days through `add_legal_term` land 12 March. Without the policy, source and pinned calendar the terms stay silent; `add_calendar_period` never touches the snapshot. Derivation equality compares histories, not digits. Every bounds composition equals itself across all five operations; the hand-written `bounds_const(5, 6)` never equals the computed sum; the lifted `ln` over `[1, 1]` equals itself but not the `ln` leaf over `1` — same interval, different history. Sufficiency publishes one cell or names two. `round_bounds` of the certified root of nine at precision zero publishes `3`; `rounds_to` agrees on the same cell; the neighbouring cell stays silent; the wide `ln 2` interval at precision ten is `NEITHER` with a `BOUNDS_INSUFFICIENT` issue naming both extreme cells. No guess is ever published.
What catches the shortcuts? Hand-writing the digits where a certified call belongs is caught by derivation comparison. Calling `exact_sqrt`, `solve_linear`, `fx_convert` or `currency_convert` is caught louder, at check time, with the two pinned codes.
The proof is the 67/67 suite, the `BOUNDS_INSUFFICIENT` and `INEXACT_DIVISION` issues pinned by name, and ten refusal reproducers whose outputs are quoted verbatim. What the example does NOT prove: that the refused names are meaningless — each refusal names its layer (support vs semantics vs version) and says nothing about the language in general. Nor does it prove that every `0.2` name is covered here — `only`, interval predicates and deontic surfaces belong to their own articles. ## 6. Changed condition Change one substantial condition: replace the lifted logarithm with the leaf logarithm as the comparison target. `LnLiftSelf` compares `ln_bounds(bounds_const(1, 1), "p8/0.1")` with the textually identical lift and fires `TRUE_ONLY`; `LnLiftLeaf` compares the same lift with the `ln_bounds(1, "p8/0.1")` leaf and stays `NEITHER`. The suite pins both (`lifted log is deterministic` passes, `lift is not the leaf` passes as silence). Same interval `[0, 0]`, different derivation, different outcome: equality reads the history, not the digits. A second change, on the calendar: ask for 2 April instead of 3 April as the month step and the rule goes silent. `start_count = next_day` is part of the answer, not of the question — the shifted date is the specified one. ## 7. Typical mistake The mistake is averaging integers and expecting twelve. Over readings 12, 9 and 15 the mean is exactly twelve in every schoolbook, yet `AvgSilent` fires nothing: `average` over `Integer` has no rounding policy to apply and refuses the quiet integer division. The observable consequence is precise — `NEITHER`, pinned green by the test `average over integers stays silent`. The fix is explicit too: average the `Money` bills with `2, "HALF_UP"`, or average `Decimal` samples. The same shape of mistake with `div_round` over a `Quantity`, or `/` with an `Integer` divisor, is refused even earlier — statically, with `LDC-E2108`. ## 8. Limits Applicability bounds, all verified against tool `law 0.1.0`, semantics `law.core/0.2`, std `0.2.0`. Refusals below are facts about this build and this profile, never claims about the language in general. `Number`, `Algebraic`, `RealExpr`, `exact_sqrt`, `pi`, `ln2`, `solve_linear`, `poly_nroots_2/3`, `poly_root_2/3`, `temp_convert`, `angle_convert` and `fx_convert` (profiles 0.4–0.8) are refused at two layers: `LDC-E2105` (not in the std v1-list) plus `LDC-E2403` (no effect descriptor). The three types alone are refused with `LDC-E2101`. Each has a frozen reproducer in `evidence/snippets/` and an open obligation: a future tool with a 0.4–0.8 implementation passes these layers, and these pins say nothing until then. `currency_convert` is named by [§260](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#260-lawstdmoney) but sits in no executable slice: same two codes, same treatment, same open obligation. `average` over `Integer`/`Rational` stays silent (no quiet division); `div_round` over `Quantity` and `/` with an `Integer` divisor are static `LDC-E2108`. `add_business_days` and `add_legal_term` over day units read the pinned calendar through the deadline policy; without policy, source and calendar they stay silent. `add_calendar_period` never reads the snapshot, but it reads `start_count` (and `month_end` for month/year steps). Rule-body folding observes only the `TRUE_ONLY` arm of `rounds_to` and `text_matches`: a wrong cell and an insufficient interval both read as silence on the head relation. The arms are told apart one level down — the term publishes the true cell, or raises `BOUNDS_INSUFFICIENT` naming both extreme cells. ## 9. Exercise Extend the water billing with a late-payment rule: a bill of 1,880 KZT split over four months is exactly 470 KZT per month, but the same bill split over three months must be loud, not rounded. Write one package rule plus two tests — (a) `truth == TRUE_ONLY` for the exact quarterly split against 470 KZT, (b) `issue(INEXACT_DIVISION)` for the three-way split — and run `law test` on the package. Checkable expectation: both new tests pass and the suite total grows by exactly two with zero failures. Second, refusal reading. Run both `law engine check` commands from section 4 and name, for each probe, the two diagnostic codes plus the layer each belongs to (name resolution vs computability proof). Then change the `ln2` probe's return type from `Rational` to `RealExpr`, predict the new diagnostic set before running, and run to confirm. Checkable solution: [full solution with checkable answers](/tutorials/northbridge/solutions/nb-23-solutions/). ## 10. Sources Northbridge role → domain template → confirmed external formalization. Full sources: the package [README](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/README.md), [package.law](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/package.law) and [tests](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/tests/numerics.lawtest); refusal pins in [evidence/snippets/](https://github.com/arxohq/law/blob/master/packs/examples/language-demo/numerics/evidence/snippets/). Water billing story → `demo.northbridge.numerics` (this article's new template): meter thresholds, quarterly bills, notice and due dates against the pinned March 2026 table. Exact arithmetic, rounding, dimensions and certified bounds → `law.std.data` [§257](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#257-lawstddata), `law.std.time` [§258](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#258-lawstdtime), `law.std.units` [§259](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#259-lawstdunits), `law.std.money` [§260](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/34-part-xxxiii-standard-library.ru.md#260-lawstdmoney); calendar steps → [§84](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/12-part-xii-events-actions-temporal-model.ru.md#84-duration-types)–[§86](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/12-part-xii-events-actions-temporal-model.ru.md#86-deadline-policy); rounding formula → [§64.2](https://github.com/arxohq/law/blob/master/spec/SPEC.ru/10-part-x-propositions-and-four-valued-support.ru.md#642-отношение-округления-над-сертифицированными-границами-errata-e-0136); version lines 0.4–0.8 → registry `spec/feature-registry.json`. No external formalization is claimed for the refused profiles: the 0.4–0.8 semantics are design decisions (DECISION-0436/0438–0441 project, 0437 accepted 30.09.2026), and this build executes the 0.2 line only. Confirmed external formalization on the 0.2 line (corpus): five-year prescription deadline (French Civil Code, prescription title) — package `fr.code_civil`, `corpus/laws/fr/code-civil/20-prescription-extinctive.law:122-129`. The period is built by a calendar step (`then echeance_prescription_le(c, add_calendar_period(depart, 5 calendar_year))`), not measured in days, with the policy carried by a case-context line in every scenario test. Evidence: `docs/research/constructs/20-deadline-calendar/corpus-forms.en.md` [§1](/tutorials/northbridge/nb-01-first-permit/#1-situation) (rated exemplary). Limit of verification: presence of the named construct at the cited lines only, confirmed by direct file read; no claim about deployment, runtime behaviour, or legal correctness.