FAILURE MAP
← Case archive

FA-62001 / Currency rounding and FX conversion / Open access

The conversion rate is rounded to four decimals before use · case 01

Large conversions at five- or six-decimal rates are off by many minor units.

Verified by executionVariant 1 · 7 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The rate is quantized to 0.0001 as if read from a NUMERIC(10,4) column.

VERIFIED REPAIR

Use the full precision of the rate string.

Unsuccessful approach: Keeping six decimals still loses precision for rates quoted with more digits.

Case contract

solve(amount_minor, from_exp, to_exp, rate): amount_minor is an integer count of source minor units, rate a decimal string giving target major units per source major unit. The target minor amount is amount_minor * rate * 10**(to_exp - from_exp), computed exactly and rounded half-even to an integer.

Why this case matters

Currency amounts must be rounded at the right stage and in the right unit, or ledgers, quotes and settlements drift by minor units.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount_minor, from_exp, to_exp, rate):
    scale = Decimal(10) ** (to_exp - from_exp)
    v = Decimal(amount_minor) * Decimal(rate).quantize(Decimal('0.0001'), rounding=ROUND_HALF_EVEN) * scale
    return int(v.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression rate-storage-scale 1', (987654321, 2, 3, '0.30712'), 3033283951),
  ('regression rate-storage-scale 2', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: cents to yen', (1000, 2, 0, '151.37'), 1514), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307)],
 [('regression rate-storage-scale 1', (123456789, 2, 2, '0.00000071'), 88),
  ('regression rate-storage-scale 2', (123456, 2, 2, '1.08347'), 133761),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('control: yen to dinar', (1, 0, 3, '0.3071'), 307), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)],
 [('regression rate-storage-scale 1', (755323, 2, 4, '7.24667'), 547357652),
  ('regression rate-storage-scale 2', (410259, 2, 2, '0.258909'), 106220),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951), ('control: cents to yen', (1000, 2, 0, '151.37'), 1514),
  ('control: float trap tie', (115, 2, 2, '1.1'), 126)],
 [('regression rate-storage-scale 1', (286850, 2, 3, '1.495849'), 4290843),
  ('regression rate-storage-scale 2', (253357, 0, 4, '18.88901'), 47856629066),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307),
  ('control: negative tie', (-3, 2, 2, '0.5'), -2)],
 [('regression rate-storage-scale 1', (395121, 3, 4, '1.362591'), 5383883),
  ('regression rate-storage-scale 2', (402529, 2, 4, '1.969810'), 79290565),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)]]
for label, args, expected in cases[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression rate-storage-scale 130330864203033283951Failed
regression rate-storage-scale 2108350000108347120Failed
partial repair guard 2088Failed
control: cents to yen15141514Passed
control: float trap tie126126Passed
control: negative exact-125-125Passed
control: yen to dinar307307Passed

SHA-256 / ce75efaf5ae2086e436a90eeb4d1e5071c4157b26930119d27f129023e4c4974

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount_minor, from_exp, to_exp, rate):
    scale = Decimal(10) ** (to_exp - from_exp)
    v = Decimal(amount_minor) * Decimal(rate).quantize(Decimal('0.000001'), rounding=ROUND_HALF_EVEN) * scale
    return int(v.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression rate-storage-scale 1', (987654321, 2, 3, '0.30712'), 3033283951),
  ('regression rate-storage-scale 2', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: cents to yen', (1000, 2, 0, '151.37'), 1514), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307)],
 [('regression rate-storage-scale 1', (123456789, 2, 2, '0.00000071'), 88),
  ('regression rate-storage-scale 2', (123456, 2, 2, '1.08347'), 133761),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('control: yen to dinar', (1, 0, 3, '0.3071'), 307), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)],
 [('regression rate-storage-scale 1', (755323, 2, 4, '7.24667'), 547357652),
  ('regression rate-storage-scale 2', (410259, 2, 2, '0.258909'), 106220),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951), ('control: cents to yen', (1000, 2, 0, '151.37'), 1514),
  ('control: float trap tie', (115, 2, 2, '1.1'), 126)],
 [('regression rate-storage-scale 1', (286850, 2, 3, '1.495849'), 4290843),
  ('regression rate-storage-scale 2', (253357, 0, 4, '18.88901'), 47856629066),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307),
  ('control: negative tie', (-3, 2, 2, '0.5'), -2)],
 [('regression rate-storage-scale 1', (395121, 3, 4, '1.362591'), 5383883),
  ('regression rate-storage-scale 2', (402529, 2, 4, '1.969810'), 79290565),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)]]
for label, args, expected in cases[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression rate-storage-scale 130332839513033283951Passed
regression rate-storage-scale 2108347100108347120Failed
partial repair guard 212388Failed
control: cents to yen15141514Passed
control: float trap tie126126Passed
control: negative exact-125-125Passed
control: yen to dinar307307Passed

SHA-256 / a6f9f68d06532fdf449233f064a88979b65438fea7a83414f1b6f70436e12857

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
from decimal import Decimal, ROUND_HALF_EVEN, ROUND_HALF_UP, ROUND_FLOOR, ROUND_CEILING, ROUND_DOWN, ROUND_UP, ROUND_HALF_DOWN
N = 1
observations = []
def solve(amount_minor, from_exp, to_exp, rate):
    scale = Decimal(10) ** (to_exp - from_exp)
    v = Decimal(amount_minor) * Decimal(rate) * scale
    return int(v.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression rate-storage-scale 1', (987654321, 2, 3, '0.30712'), 3033283951),
  ('regression rate-storage-scale 2', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: cents to yen', (1000, 2, 0, '151.37'), 1514), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307)],
 [('regression rate-storage-scale 1', (123456789, 2, 2, '0.00000071'), 88),
  ('regression rate-storage-scale 2', (123456, 2, 2, '1.08347'), 133761),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('control: yen to dinar', (1, 0, 3, '0.3071'), 307), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)],
 [('regression rate-storage-scale 1', (755323, 2, 4, '7.24667'), 547357652),
  ('regression rate-storage-scale 2', (410259, 2, 2, '0.258909'), 106220),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88),
  ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951), ('control: cents to yen', (1000, 2, 0, '151.37'), 1514),
  ('control: float trap tie', (115, 2, 2, '1.1'), 126)],
 [('regression rate-storage-scale 1', (286850, 2, 3, '1.495849'), 4290843),
  ('regression rate-storage-scale 2', (253357, 0, 4, '18.88901'), 47856629066),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: float trap tie', (115, 2, 2, '1.1'), 126),
  ('control: negative exact', (-250, 2, 2, '0.5'), -125), ('control: yen to dinar', (1, 0, 3, '0.3071'), 307),
  ('control: negative tie', (-3, 2, 2, '0.5'), -2)],
 [('regression rate-storage-scale 1', (395121, 3, 4, '1.362591'), 5383883),
  ('regression rate-storage-scale 2', (402529, 2, 4, '1.969810'), 79290565),
  ('partial repair guard 1', (100000000, 2, 2, '1.0834712'), 108347120),
  ('partial repair guard 2', (123456789, 2, 2, '0.00000071'), 88), ('control: negative tie', (-3, 2, 2, '0.5'), -2),
  ('control: zero', (0, 2, 0, '151.37'), 0), ('control: precise rate', (123456, 2, 2, '1.08347'), 133761),
  ('control: large', (987654321, 2, 3, '0.30712'), 3033283951)]]
for label, args, expected in cases[N - 1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression rate-storage-scale 130332839513033283951Passed
regression rate-storage-scale 2108347120108347120Passed
partial repair guard 28888Passed
control: cents to yen15141514Passed
control: float trap tie126126Passed
control: negative exact-125-125Passed
control: yen to dinar307307Passed

SHA-256 / 926903d463caa9ff299e3e68abfa61627389bd3288975ca64fb9b29af3bde475

Verification & scope

A deterministic, bounded teaching model with a stipulated toy contract; it makes no claim of conformance to any real regulation, standard, or institution's rules. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:47:00.517591+00:00.

Case digest / 02440de222d63391febbf55946f39ee8c6ca8a5e72de1ded1a405f8134a13ee0