# Preserve the amount across a money contract

Original LockedIn Labs instruction · 50-minute financial data-contract lab · reviewed 27 October 2026.

Two systems send `12500`. Do they agree? One means USD 125.00 in minor units;
the other means USD 12,500 in major units. A numeric equality check passes while
the business amount differs by a factor of one hundred. You will reproduce this
failure, an actual JSON precision loss and four explicit rounding policies.

This is a read-only engineering workshop using synthetic nonnegative amounts.
It creates no account, fee, posting or financial transaction. You need Node.js 24
and its standard library; no packages, credentials, database or network.
Start with [financial reconciliation](/training/guides/financial-reconciliation)
if case, posting and credit meaning are new.

## 1. Name the amount before comparing it

An amount needs a currency, unit and exact representation. A **major unit** is
the named currency unit; a **minor unit** is the scale used by the agreed currency
contract. **Scale** is the number of decimal places represented. It is separate
from a software number type's precision and the business policy for rounding.

| Payload meaning | Equivalent value in this fixture | Required context |
| --- | --- | --- |
| USD major `125.00` | USD minor `12500` | Currency USD, scale 2 |
| JPY major `125` | JPY minor `125` | Currency JPY, scale 0 |
| KWD major `1.234` | KWD minor `1234` | Currency KWD, scale 3 |

These three minor-unit facts were checked against SIX's ISO 4217 List One,
published 17 September 2026. They are a bounded authored snapshot, not a complete
or automatically updated currency service. Provider APIs and product contracts
may impose additional scale or settlement rules. Never infer a universal
“multiply by one hundred” rule or perform an implicit currency conversion.

The runner accepts exactly `amount`, `currency` and `unit`. The amount is a
canonical decimal string; the unit is explicitly `major` or `minor`. Minor-unit
strings must be integers. This fixture rejects signs, exponent notation, commas,
leading zeros, unsupported currency and excess fractional places. Those choices
are its interface contract, not the grammar of every financial API.

## 2. Follow representation and policy separately

For ordinary bounded integers, JavaScript Number can be exact. Its binary
floating-point representation does not exactly represent every decimal fraction,
and integers above its safe range can become indistinguishable. An exact decimal
string can preserve the original digits at an API boundary; a parser still needs
bounds, currency, scale and validation before it can use them.

The lab converts validated strings into BigInt minor units. BigInt performs
integer arithmetic; it does not invent currency meaning, decimal policy,
authorization or a financial ledger. Do not convert an already-rounded Number
to a string and assume its lost digits returned.

The visual has six original frames grounded in actual Node.js results. The
browser explains those observations; download the program to execute them.
The written ledger below keeps the same distinctions available without the map.

## 3. Run six amount-contract comparisons

Download [the money-contract runner](/templates/fde-money-contracts.mjs). Keep
original and changed reports separately:

```sh
node --version
node fde-money-contracts.mjs > money-original.json
node fde-money-contracts.mjs --rounding-mode half-even > money-even.json
node fde-money-contracts.mjs --rounding-scope total > money-total.json
```

The runner uses actual `JSON.parse`, Number operations and BigInt arithmetic.
It is a bounded teaching parser for nonnegative inputs, not a production money
library. `node_version` records the runtime. All exact integer results in the
report are serialized as strings so reporting does not reintroduce Number loss.

### The written ledger

| Case | Deliberately unsafe observation | Guarded or explicit-policy observation |
| --- | --- | --- |
| `currency-scale` | Assuming every currency has two decimal places loses meaning | USD 125.00 → 12500; JPY 125 → 125; KWD 1.234 → 1234 |
| `unit-mismatch` | Raw Number values both equal 12500 | USD minor 12500 differs from USD major 12500, which is minor 1250000 |
| `currency-mismatch` | Raw Number values again match | USD and JPY remain different currencies; no FX conversion occurs |
| `binary-arithmetic` | `0.1 + 0.2` produces 0.30000000000000004, unequal to 0.3 | Exact string inputs become 10 + 20 = 30 minor units, USD 0.30 |
| `json-integer` | Numeric token 9007199254740993 parses as 9007199254740992 | Original string preserves 9007199254740993; one minor unit was lost on the Number path |
| `rounding-policy` | “Round the fee” omits mode and aggregation scope | Three intermediate USD 0.005 values yield different explicitly calculated policies |

Inspect `json-integer.numeric_wire`, `parsed_number_text` and
`converting_after_loss`. The strict interface refuses the Number input, but
converting that rounded value to a string afterward still supplies the wrong
digits. Validation cannot reconstruct source information already discarded.
The original string must survive the producer, transport, parser and storage
contract together.

A USD major amount of `0.005` exceeds this currency-scale contract. It is refused
rather than silently rounded. The separate rounding exercise explicitly declares
higher-precision intermediate values and a policy for producing final minor
units. Distinguish an invalid amount field from an authorized calculation input.

## 4. Make rounding mode and scope reviewable

The fictional calculation has three nonnegative intermediate values, each USD
0.005. They use scale 3 until conversion to scale-2 USD cents. No real fee rule,
regulatory interpretation or recommendation is supplied.

**Half-up** rounds a nonnegative exact halfway value upward. **Half-even** rounds
a halfway value toward an even retained integer. **Line** scope rounds each
input first and then adds the minor units. **Total** scope adds the exact
intermediates first and rounds once. Both choices must come from the responsible
business contract; the smaller or more convenient result is not automatically
correct.

| Mode | Scope | Actual selected minor units | USD display |
| --- | --- | --- | --- |
| half-up | line | 3 | 0.03 |
| half-up | total | 2 | 0.02 |
| half-even | line | 0 | 0.00 |
| half-even | total | 2 | 0.02 |

The default selects half-up/line. Change only the mode and predict `0` rather
than `3`; change only the scope from the original and predict `2`. All other
five cases retain their inputs and results. `alternatives` calculates all four
policies for comparison. `line_minor_if_rounded` is a hypothetical per-line view;
for total scope, those values are not added to produce the selected amount.

The rounder handles only nonnegative thousandths-to-cents in this exercise.
Negative adjustments, reversals, tax, FX, cash rounding and residual allocation
require their own agreed rules and tests. A display formatter must not silently
become the accounting calculation.

## 5. Receive the amount contract as an enterprise pod

Use builder, application/ledger owner and receiving engineer roles. Combine roles
explicitly for smaller groups; label solo work self-review. The receiver chooses
one changed policy, makes a prediction first and reproduces it without author
takeover. Each person retains their own evidence and assistance.

| Role | Receiving responsibility |
| --- | --- |
| Builder | Locate exact input bytes, parser version, normalized units and the earliest point where precision could be lost |
| Application/ledger owner | Agree currency metadata, amount sign/direction, units, precision, rounding mode/scope and treatment of unresolved differences |
| Receiving engineer | Reproduce one mismatch and one controlled policy change, then return an observation with its remaining contract question |
| Platform/integration owner | Verify actual serializers, database types, consumers, limits and deployed source/version coverage before adoption |

```text
My role / exact runner and command:
Prediction before the comparison:
Original wire digits / type / currency / unit:
Normalized minor units / source scale and version:
Observed mismatch or actual selected rounding policy:
One changed condition / prediction before that run:
Changed observation kept separately:
Receiving action actually attempted / assistance:
Unresolved contract / next accountable role and bounded action:
```

Matching amounts do not prove the same transaction, posting state, credit kind or
case outcome. Carry the amount evidence into the existing reconciliation contract;
retain any exception for its authorized owner. A local run is not an independent
review, qualification, client placement decision or permission to post money.

## 6. Defend the boundary in an interview

Why can `12500 === 12500` be a false business match? Why can converting Number to
BigInt after JSON parsing preserve the wrong integer? Why does exact integer
arithmetic still need currency and rounding rules? Which consumer or storage
change would invalidate your proof? Answer using the recorded inputs and results.

Primary references checked 27 October 2026:

- [SIX ISO 4217 currency metadata and current lists](https://www.six-group.com/en/products-services/financial-information/market-reference-data/data-standards.html), including [List One XML](https://www.six-group.com/dam/download/financial-information/data-center/iso-currrency/lists/list-one.xml).
- [ECMAScript Number safe-integer behavior](https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_safe_integer).
- [RFC 8259 number interoperability](https://www.rfc-editor.org/rfc/rfc8259#section-6).

The parser, fixture policies and examples are original LockedIn Labs instruction.
Continue with [financial reconciliation](/training/guides/financial-reconciliation)
and its protected [Academy lesson](/app/academy?course=financial-services-operations&lesson=reconciliation&view=focus)
under existing training access.
