Skip to content
docs
Arxo ↗

Expressions and quantities: pitfalls

For LLMs12 sections

Wrong forms, silent outcomes, LDC-E diagnostics, typical mistakes of formalizers and AI agents, how to detect them.

1. Sum smaller than expected on equal summands

Section titled “1. Sum smaller than expected on equal summands”

sum(collect a: Money …) counts distinct values (Set); two equal incomes are one element, and the sum silently undercounts: no refusal, no warning. Write collect all a: Money, m: Employee where … — the second variable tells apart substitutions with equal values. Statics cannot see the difference: guard it with a test on two equal facts. The research.expressions.counted_threshold example deliberately gives three members equal 50 000 KZT payouts — the summary must be 150 000, not 50 000.

LDC-E1317: in collect all a: Money where household_member(h, m) … the variable m comes from nowhere — declare it as a second comprehension binder (collect all a: Money, m: Employee where …).

n >= 10.5 with n: Integer, Integer + Rational, Rational + Decimal, Date == Instant — LDC-E2108. The threshold in the fact’s kind; take a share one way for the whole computation.

Money / Integer, Integer / Decimal — LDC-E2108: a money divisor is Decimal (12.0, not 12); same for div_round. A variable divisor n: Integer is lifted by multiplying n * 1.0, not by a separate “share” fact: feeding a share as a case fact takes the act’s prescribed computation out of the act.

round(x) without precision/mode — LDC-E2116: both arguments mandatory, mode — the "HALF_UP" string. Type Percentage — LDC-E2101: a percent constant is declared Decimal (const BONUS_RATE: Decimal = 25 percent), because 25 percent is the exact Decimal 0.25.

6. Silent execution refusals instead of statics

Section titled “6. Silent execution refusals instead of statics”

g < Senior, 500 kg > 100 m, 1000 KZT + 5 USD — check stays silent, the refusal comes at execution (TYPE_ERROR, CURRENCY_MISMATCH): the rule does not fire, the case carries an issue. Guard with a scenario test: a positive test shows the norm fires, a negative one shows the silence is exactly NEITHER, not a refusal.

sum over an empty List is EMPTY_AGGREGATE, not 0 KZT: an empty sum has no currency, and the core does not invent one. Three resolutions: a norm-side policy — sum(collect all …, empty: 0 KZT); an explicit aloud refusal — empty: error (indistinguishable in CLIR from no policy); the zero case by a second rule with not_known or closure. The policy kind must match the element kind: empty: 0 over a Money collection — LDC-E2119, not “zero in tenge”. empty on count and precision/rounding, not on average — LDC-E2120.

round(base * div_round(a, b, 6, …), 2, …) is double rounding, diverging from the norm: rounding the share separately and then the total is a mistake, not caution. The rounding is one — over the exact result (div_round(base * (a / 1 KZT), b / 1 KZT, p, mode)). A decimal share approximation (s * 0.6667) is the same mistake one class down: 30 000 × 0.6667 = 20 001, not 20 000.

9. Bare domain constant instead of a declaration

Section titled “9. Bare domain constant instead of a declaration”

A name in term position resolving to no declaration is an unresolved name, static refusal LDC-E1330: a “bare domain constant” is not a term form. An individual is named only by declaration: const KZ: Jurisdiction = entity_ref("…") — a constructor gives the value, const gives the name. AI agents love writing jurisdiction and currency names straight into terms — caught only by this check.

A constant through itself is a static error, not a value: CYCLIC_CONSTANT before any conclusion. A constant through a constant is legal and unfolds recursively; a cycle is not.

  • Writing Money / Integer (“sum over a month count”) instead of div_round(s, n * 1.0, p, mode) — item 4.
  • Declaring a percent with type Percentage instead of Decimal — item 5.
  • Counting sum(collect …) as a charge summary instead of collect all — item 1.
  • Converting units by division (x / 3.6) instead of convert(value, num, den, unit); attaching a tag by multiplication instead of with_unit.
  • Expecting round(s * (2 / 3), …) to save the total: the product fails with INEXACT_DIVISION before round ever sees it — the norm’s exact fraction needs div_round(s * 2, 3.0, p, mode).

12. Off-grammar pattern and division by zero (from page 08-expressions)

Section titled “12. Off-grammar pattern and division by zero (from page 08-expressions)”
  • text_matches(t, …) with other than two arguments — LDC-E2103; a literal pattern outside the pattern grammar — LDC-E2117.
  • / and mod by zero — DIVISION_BY_ZERO: an execution refusal, not a static one — the rule does not fire, the case carries the issue.
  • Inexact money and quantity quotients without a policy (100 KZT / 3.0, 100 KZT * (1 / 3)) — INEXACT_DIVISION: divide under div_round with an explicit policy (see item 8).

Documentation for Arxo. Writings — blog.arxo.io.

Anonymous visit counts on stats.arxo.io, no cookies.