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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression rate-storage-scale 1 | 3033086420 | 3033283951 | Failed |
| regression rate-storage-scale 2 | 108350000 | 108347120 | Failed |
| partial repair guard 2 | 0 | 88 | Failed |
| control: cents to yen | 1514 | 1514 | Passed |
| control: float trap tie | 126 | 126 | Passed |
| control: negative exact | -125 | -125 | Passed |
| control: yen to dinar | 307 | 307 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression rate-storage-scale 1 | 3033283951 | 3033283951 | Passed |
| regression rate-storage-scale 2 | 108347100 | 108347120 | Failed |
| partial repair guard 2 | 123 | 88 | Failed |
| control: cents to yen | 1514 | 1514 | Passed |
| control: float trap tie | 126 | 126 | Passed |
| control: negative exact | -125 | -125 | Passed |
| control: yen to dinar | 307 | 307 | Passed |
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression rate-storage-scale 1 | 3033283951 | 3033283951 | Passed |
| regression rate-storage-scale 2 | 108347120 | 108347120 | Passed |
| partial repair guard 2 | 88 | 88 | Passed |
| control: cents to yen | 1514 | 1514 | Passed |
| control: float trap tie | 126 | 126 | Passed |
| control: negative exact | -125 | -125 | Passed |
| control: yen to dinar | 307 | 307 | Passed |
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