hamsa software
← All articles
Automation7 min read

1250 is not a complete amount

A bounded GBP/JPY converter shows why currency, scale and source representation must survive each interface, even when the arithmetic is exact.

Two suspended pans hold differently sized circles on a tilted beam, suggesting a number whose meaning depends on its unit.

The hundredfold error can be perfectly valid arithmetic

1250 can mean £12.50 or £1,250.00 without a single arithmetic error. Pence and pounds give the same integer different meanings, and more precise arithmetic cannot recover the missing unit. The integration has to bring that information across the interface. It also needs to remember whether conversion has already happened, or the next helpful normalisation step may faithfully multiply the wrong quantity again.

That is why amount should not travel alone. A useful internal representation carries currency, unit or scale and value, with an explicit conversion at each external interface. The same integer has different display meaning under different scales, and the same major-unit amount may require a different integer for different currencies or provider operations. A field name such as total is useful to a human reading one screen, but inadequate as the entire contract between two systems.

The example converter accepts major-unit decimal text for just GBP at two decimal places and JPY at zero. It returns tagged integer minor units. These are executed representation checks on invented values, with no payment submission or financial record involved. The small supported set lets us follow each conversion fully. Other currencies and provider exceptions need their actual rules added deliberately, rather than borrow apparent support from a dictionary that happens to contain a few familiar currency codes.

Preserve the source meaning through conversion

For input text 12.50, currency GBP and unit major, the converter returns currency GBP, unit minor, scale two and value 1250. It also retains the original text and source-unit label. The record can therefore be read as 1,250 units of one hundredth of a pound. This is more informative than replacing 12.50 with 1250 and expecting every later caller to remember whether multiplication has already happened.

The converter accepts a limited decimal spelling and input length, then uses Decimal with a local precision allowance, without passing through binary float.[1] It multiplies by the declared scale and rejects a fractional minor unit. The length bound keeps accepted values within the context chosen for that arithmetic; supporting longer text would require revisiting that choice. Endpoint amount limits are a separate check. A value can be represented exactly here and still exceed the range of a particular charge request, just as a well-represented negative value may be inappropriate for that operation.

Rejecting GBP 12.501 is a decision about authority as much as precision: conversion to integer pence would require rounding, and this converter has no agreed rounding rule. JPY 1.5 fails for the same reason under its zero-decimal contract. A real calculation may need rounding at line level, after aggregation or at the provider request. Choose and name that stage. Quietly rounding merely to make the conversion succeed would conceal a policy decision inside what callers thought was a representation change.

Zero and signed values are checked too. GBP 0 maps to zero pence; GBP −12.50 maps to minus 1,250 in this representation model. That does not mean a charge endpoint accepts a negative amount or that the number authorises a refund. Valid representation, permitted operation and business approval are different properties. Keeping a signed money type can be useful internally while a particular API adapter separately restricts allowed signs, ranges and operations for its request.

The provider contract is more specific than a currency name

Stripe’s current currency documentation specifies the required smallest-unit representation, including 500 JPY being submitted as 500.[2] Multiplying every currency by 100 would change that request to a different amount. The converter’s GBP/JPY mapping therefore selects the scale before doing arithmetic, rather than inferring it from the magnitude of the number. An adapter must then apply the actual endpoint’s contract. A shared amount field in the application is useful only if the final request still says which currency and unit the receiving operation expects.

ISK shows why the provider contract matters beyond the usual currency scale. Stripe documents its compatibility representation with two-decimal-style amounts and fractional digits fixed at zero.[2] The example rejects ISK as unsupported. That is more useful than applying a generic zero-decimal rule and announcing a successful conversion. The adapter has to know what this provider and this operation expect, including the awkward cases that a general currency exponent cannot settle.

An adapter can make those responsibilities explicit. It accepts the application's typed amount, checks that the endpoint supports the currency and operation, converts to the required request representation once, and records enough information to explain that request. An inbound adapter performs the reverse interpretation under its own endpoint contract. Do not assume a provider's dashboard export and payment request use identical units because both contain an amount field. Inspect each interface and give its representation a distinct name in code and documentation.

Currency case and textual presentation also belong to the boundary. The internal example uses uppercase currency tags for readability; a real adapter must follow its endpoint's required spelling. Formatting a value for a person is another operation again. A display string containing a currency symbol, grouping separators or locale-specific punctuation should not be fed back into numerical conversion without a declared grammar. Otherwise a successful display change can silently become an input-format change for an integration that was parsing the screen's text.

The unit determines what 1250 means

Executed bounded representation converter; no payment, refund or exchange-rate operation was performed.

Scroll sideways to see all columns.

Input / declared unitCurrency / scaleNormalised integerOutcome
12.50 majorGBP /21250 minor£12.50 preserved
0 majorGBP /20 minorZero preserved
−12.50 majorGBP /2−1250 minorRepresentation only; no charge permission
500 majorJPY /0500 minorNo×100 conversion
12.501 majorGBP /2RejectedFraction of minor unit
1.5 majorJPY /0RejectedFraction of minor unit
5 majorISK / unsupportedRejectedProvider exception not implemented
A raw 1250 could mean £12.50 in pence or £1,250.00 in pounds. The converter retains source text/unit and rejects precision it would otherwise need to round.

Make an accidental second conversion difficult

A common failure is multiplying by 100 twice. The first adapter correctly turns £12.50 into 1250 pence; a later helper named normaliseAmount assumes 1250 is still pounds and produces 125000. Each arithmetic step is simple and individually exact. A type or record that distinguishes MajorAmount from MinorAmount can reject that second conversion or at least force its caller to state the assumption. A bare number cannot explain whether the transformation has already occurred.

If £1,250 appears where £12.50 was expected, follow the value through its representations: received text, source unit, tagged core value and exact outbound request. That sequence can distinguish an intake misunderstanding from a second conversion or a display error. The final database integer alone may be equally consistent with two explanations. Keeping appropriate provenance gives the investigation something concrete to compare, rather than leaving people to argue about whether 1250 looks like a plausible order.

Do not use plausibility bounds as the primary unit detector. A hundredfold error may create an obviously large amount in one case and a completely ordinary amount in another. An application can still enforce documented bounds for a particular operation, but those checks do not establish that the unit is correct. Likewise, summing unlike currencies because both values are integers creates a meaningless total. Conversion between currencies needs its own rate, timing and rounding contract; none is supplied by changing the currency label on an amount record.

The checks name what the values mean: majorGBP 12.50, zero, a negative representation, JPY 500, rejected fractional minor units and unsupported ISK. A real adapter also needs its exact outbound representation and inverse display interpretation checked. Expecting only the integer 1250 would miss the distinction we started with. State the unit in the assertion, and a changed interface or accidental second conversion can fail for the right reason even while the multiplication remains entirely correct.

The accidental second conversion

A failure path discussed in the amount-representation example, not a payment execution.

Follow the labelled arrows. On a narrow screen, scroll sideways if needed.

The accidental second conversionInbound major amount: GBP 12.50. The first adapter declares the source unit and converts once. Tagged minor amount: GBP / scale 2 / 1250. The core retains the fact that the value is already pence. Untagged helper produces 125000. If a helper assumes 1250 is pounds and multiplies by 100 again, exact arithmetic changes the intended amount. From Inbound major amount: GBP 12.50 to Tagged minor amount: GBP / scale 2 / 1250: One declared conversion. From Tagged minor amount: GBP / scale 2 / 1250 to Untagged helper produces 125000: Unit tag lost; helper converts again.One declaredconversionUnit tag lost;helperconverts againInbound major amount:GBP 12.50The first adapter declaresthe source unit and convertsonce.Tagged minor amount:GBP / scale 2 / 1250The core retains the factthat the value is alreadypence.Untagged helperproduces 125000If a helper assumes 1250 ispounds and multiplies by 100again, exact arithmeticchanges the intended amount.
MajorAmount and MinorAmount distinguish transformation stages. Endpoint signs, ranges and permitted operations remain adapter checks.

Keep the simpler case explicit too

A single-currency internal tool may reasonably use integer pence throughout its core. It does not need a general-purpose money framework merely to be careful. Name the unit, document the ingress and egress conversions, reject unsupported precision and keep formatting separate. Complexity becomes worthwhile when several currencies, provider-specific representations or intermediate calculations would otherwise leave assumptions scattered through ordinary numbers.

The person investigating 1250 should not have to infer its unit from the customer’s usual spending. Currency and scale tell them the quantity; precise arithmetic then protects operations within that representation. The original text and source-unit tag explain how the example reached its core value, and the endpoint adapter owns the final provider-specific conversion. Once those meanings travel together, a simple integer can remain simple without making the next reader guess what it counts.

Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.

See the Payment Operations Pilot

References & further reading

Primary sources behind this article. Living documentation may change.

  1. Python: Decimal construction and precision
  2. Stripe: currency amount representations and exceptions
Hamsa Jama

Practical engineering. A human perspective.

More about Hamsa Software and the person behind it. Editorial notes.

A few more things
worth a read.

Back to the reading room
Automation6 min read

A budget that three workers can share

Enforce task limits through atomic reservations and repeat-safe settlement, including the uncomfortable case where a timed-out operation may still be spending.

Automation7 min read

What exactly did that approval authorise?

Bind an assistant’s approval to the action, target and material revision, then enforce that binding when the write happens.