Numeric Precision in Structured LLM Output: Stop Rounding From Becoming a Production Bug
Valid JSON can still corrupt money, measurements, IDs, and percentages. Design schemas and validators so an LLM never gets to improvise numeric precision.
Schema-constrained output solves syntax, not arithmetic. A model can return perfectly valid JSON while changing 19.995 to 20.00, expressing a 64-bit identifier in scientific notation, confusing a percentage with a fraction, or dropping the sign from a balance.
Once that payload reaches a payment, laboratory calculation, inventory system, or analytics pipeline, a plausible number becomes a production defect. Numeric safety requires representations and invariants that remove interpretation from the model.
Classify numbers before designing the schema
Do not use one generic number type for every domain. Classify fields by meaning:
- money: currency plus amount in minor units or a decimal string;
- counts: bounded integers with explicit minimums;
- measurements: value, unit, precision, and observation context;
- rates: fraction, percentage, or basis points—never an unlabeled decimal;
- identifiers: strings, even when every character is numeric;
- timestamps: validated ISO strings or integer epochs with a named unit.
The schema should make incompatible interpretations impossible.
{
"amountMinor": 1999,
"currency": "USD",
"taxBasisPoints": 825,
"accountId": "90071992547409931234"
}
This object does not ask downstream code whether 8.25 means a fraction or percent, and it prevents JavaScript from rounding the account identifier.
Keep identifiers out of floating-point types
JavaScript numbers cannot exactly represent every integer above 9,007,199,254,740,991. Other runtimes and databases have different limits. A model may reproduce a long numeric ID accurately as text, only for parsing to change its final digits.
Represent phone numbers, card references, order numbers, postal codes, and database keys as strings. Validate their format separately. Leading zeros and check digits are part of identity, not mathematical value.
If a tool contract already exposes unsafe numeric IDs, normalize them to strings at the adapter boundary before placing them in model context.
Choose a money representation deliberately
Binary floating point cannot exactly represent many decimal fractions. For fixed currencies, integer minor units are simple and reliable: $19.99 becomes 1999 cents. Currency must travel with the amount because minor-unit rules differ.
For arbitrary precision or assets with many decimal places, use a canonical decimal string and a decimal library. Define maximum scale and magnitude.
const Money = z.object({
amount: z.string().regex(/^-?(0|[1-9]\d*)(\.\d{1,8})?$/),
currency: z.enum(["USD", "EUR", "MYR", "BTC"]),
});
Do not let the model choose a rounding mode. The authoritative service should apply documented rules such as half-even or half-up at a specified step.
Separate extraction from calculation
Models are useful for locating a value in unstructured text and mapping it to a field. Deterministic code should perform arithmetic, currency conversion, tax calculation, aggregation, and threshold comparison.
Ask the model to return inputs and provenance:
{
"quantity": "3",
"unitPrice": "12.49",
"currency": "USD",
"sourceSpan": "3 units at $12.49 each"
}
Then compute the total with a decimal library. If the source is ambiguous, the model should return an explicit ambiguity state rather than invent a precision level.
Validate units and ranges
A valid value can still use the wrong unit. Store {value, unit} and restrict units by field. Convert only in deterministic code.
Range checks should reflect the domain, not merely the storage type. A temperature, duration, discount, or confidence score needs meaningful bounds. Validate relationships too: subtotal plus tax should reconcile with total within an allowed tolerance; a probability distribution should sum to one under a defined rule.
Avoid “reasonable-looking” repair. If a model returns 250 where the schema expects a fraction from zero to one, do not silently divide by 100. Reject and request a corrected extraction with the source evidence.
Preserve source precision
Reporting more digits than the source implies creates false accuracy. If a document says “about 2.3 million,” the extraction should not become 2,300,000.0000 without a qualifier.
Store the raw span, normalized value, stated unit, and approximation flag. For scientific measurements, include uncertainty or significant figures when provided. Formatting for display should happen after the value’s semantics are fixed.
Test edge cases systematically
Your evaluation set should include:
- values just above safe-integer boundaries;
- negative zero and signed amounts;
- commas used as decimal or thousands separators;
- very small and very large exponents;
- currencies with zero, two, and three minor digits;
- percentages expressed as
8%,0.08, and 800 basis points; - quantities with conflicting units;
- totals that almost—but do not—reconcile.
Property-based tests can generate magnitudes and scales the hand-written suite misses. Run the same payload through model output parsing, application types, serialization, queues, and database storage.
Observe numeric corrections in production
Log validation failure classes without exposing sensitive values. Track unsafe integers, excess scale, range failures, unit mismatches, and reconciliation errors by prompt release and model policy.
If a repair pass is allowed, record the original payload and whether correction used source evidence. Cap attempts. A repeated mismatch should stop the workflow rather than gradually mutate a financial value.
The takeaway
Structured output guarantees shape only when the schema expresses meaning. Keep identifiers as strings, money in integer minor units or canonical decimals, rates in named representations, and calculations in deterministic code. Validate range, unit, precision, and reconciliation before any side effect. A number that parses is not necessarily a number you can trust.
> Want more like this?
Get the best AI insights delivered weekly.
By subscribing, you agree to our Privacy Policy. You can unsubscribe at any time.
> Related Articles
pgvector Can Return 4 of Your 10 Matches—Where Did the Rest Go?
A filtered vector search can quietly starve your AI app of evidence. pgvector has a fix, but builders need to measure recall and latency before trusting it.
Agent Memory Consolidation Scheduling: When to Merge, Summarize or Forget
Your agent doesn't need more memory — it needs a maintenance schedule. Here's how to build merge, summarize, and decay policies that keep long-term agents sharp instead of senile.
LLM Output Schema Evolution: Migrating Producers and Consumers Without a Flag Day
Your JSON schema will change. Here's how to evolve LLM output contracts safely — with dual readers, shadow validation, and staged rollouts instead of a risky big-bang cutover.
Most Read
- 1 Kimi K3.1 Pro: China's Open-Weight Model Just Caught the Frontier
- 2 OpenAI Is Buying the Bottleneck: Can Orchestra Bio Cut a 15-Year Drug Timeline?
- 3 1,068 Tokens Per Second: Can Mercury 3 Change How LLMs Generate Text?
- 4 Same Price, Better Coding: How Good Is DeepSeek V4.1 Flash 1017?
- 5 Used OpenCode? Should You Rotate Your API Key After the Console Breach?
Latest News
- Claude Code Left Secrets in 1,122 GitHub Files—Check This Setting
- Gemini Omni 1.2 Flash Makes 15-Second Video Cheaper—Then Comes the Edit
- GPT-6 Cyber Can Build Exploits—But Builders Face a Different Test
- 1,122 GitHub Files Exposed Secrets—What Did “Don't Ask Again” Save?
- Gemini Omni 1.2 Flash Offers $1.05 Clips—What Does That Buy You?
- GPT-6 Cyber Hits 48.5% on ExploitGym—Who Gets the Keys?
Tags
> Stay in the loop
Weekly AI tools & insights.