FA-61861 / Currency rounding and FX conversion / Open access
The amount passes through binary floating point before minor-unit rounding · case 01
'2.675' USD rounds to 2.67 because the binary value is slightly below the tie.
ROOT CAUSE
The string is converted with Decimal(float(amount)), importing the binary approximation error into the tie decision.
VERIFIED REPAIR
Construct the Decimal directly from the exact amount string.
Unsuccessful approach: Round-tripping through repr(float(...)) repairs short literals but still loses digits beyond double precision.
Case contract
solve(amount, ccy): amount is an exact decimal string, ccy an ISO-like code that is trimmed and upper-cased. Minor-unit exponents: USD 2, EUR 2, JPY 0, KRW 0, KWD 3, BHD 3, CLF 4. Round the exact decimal half-even at the currency exponent and return a plain string with exactly that many fraction digits; a zero result is unsigned. Unknown codes return 'ERR:unknown-currency'.
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, ccy):
exps = {'USD': 2, 'EUR': 2, 'JPY': 0, 'KRW': 0, 'KWD': 3, 'BHD': 3, 'CLF': 4}
code = ccy.strip().upper()
if code not in exps: return 'ERR:unknown-currency'
e = exps[code]
x = Decimal(float(amount))
q = x.quantize(Decimal(1).scaleb(-e), rounding=ROUND_HALF_EVEN)
if q == 0: q = abs(q)
return format(q, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-decimal-input 1', ('2.675', 'USD'), '2.68'),
('regression exact-decimal-input 2', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: cents tie to even', ('2.665', 'USD'), '2.66'), ('control: yen tie', ('1235.5', 'JPY'), '1236'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346')],
[('regression exact-decimal-input 1', ('1.0005000000000000000001', 'KWD'), '1.001'),
('regression exact-decimal-input 2', ('2.665', 'USD'), '2.66'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00'),
('control: integer input', ('7', 'BHD'), '7.000')],
[('regression exact-decimal-input 1', ('10.0005', 'KWD'), '10.000'),
('regression exact-decimal-input 2', ('12.345', ' eur '), '12.34'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: integer input', ('7', 'BHD'), '7.000'), ('control: unknown code', ('1.00', 'ABC'), 'ERR:unknown-currency'),
('control: large amount', ('123456789.125', 'USD'), '123456789.12'),
('control: negative tie', ('-2.5', 'KRW'), '-2')],
[('regression exact-decimal-input 1', ('48525.1705', 'Kwd '), '48525.170'),
('regression exact-decimal-input 2', ('38174.12705', 'CLF'), '38174.1270'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: negative tie', ('-2.5', 'KRW'), '-2'), ('control: cents tie to even', ('2.665', 'USD'), '2.66'),
('control: yen tie', ('1235.5', 'JPY'), '1236'), ('control: dinar three places', ('10.0005', 'KWD'), '10.000')],
[('regression exact-decimal-input 1', ('41458.105', 'EUR'), '41458.10'),
('regression exact-decimal-input 2', ('24714.6255', 'KWD'), '24714.626'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00')]]
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 exact-decimal-input 1 | 2.67 | 2.68 | Failed |
| regression exact-decimal-input 2 | 0.12 | 0.13 | Failed |
| partial repair guard 2 | 1.000 | 1.001 | Failed |
| control: cents tie to even | 2.67 | 2.66 | Failed |
| control: yen tie | 1236 | 1236 | Passed |
| control: dinar three places | 10.001 | 10.000 | Failed |
| control: unit of account four places | 1.2346 | 1.2346 | Passed |
SHA-256 / e7e556c51a24c199f8cb2247ff6b971c58f4e15c081eaf4ebcc4113b39f9aa44
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, ccy):
exps = {'USD': 2, 'EUR': 2, 'JPY': 0, 'KRW': 0, 'KWD': 3, 'BHD': 3, 'CLF': 4}
code = ccy.strip().upper()
if code not in exps: return 'ERR:unknown-currency'
e = exps[code]
x = Decimal(repr(float(amount)))
q = x.quantize(Decimal(1).scaleb(-e), rounding=ROUND_HALF_EVEN)
if q == 0: q = abs(q)
return format(q, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-decimal-input 1', ('2.675', 'USD'), '2.68'),
('regression exact-decimal-input 2', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: cents tie to even', ('2.665', 'USD'), '2.66'), ('control: yen tie', ('1235.5', 'JPY'), '1236'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346')],
[('regression exact-decimal-input 1', ('1.0005000000000000000001', 'KWD'), '1.001'),
('regression exact-decimal-input 2', ('2.665', 'USD'), '2.66'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00'),
('control: integer input', ('7', 'BHD'), '7.000')],
[('regression exact-decimal-input 1', ('10.0005', 'KWD'), '10.000'),
('regression exact-decimal-input 2', ('12.345', ' eur '), '12.34'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: integer input', ('7', 'BHD'), '7.000'), ('control: unknown code', ('1.00', 'ABC'), 'ERR:unknown-currency'),
('control: large amount', ('123456789.125', 'USD'), '123456789.12'),
('control: negative tie', ('-2.5', 'KRW'), '-2')],
[('regression exact-decimal-input 1', ('48525.1705', 'Kwd '), '48525.170'),
('regression exact-decimal-input 2', ('38174.12705', 'CLF'), '38174.1270'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: negative tie', ('-2.5', 'KRW'), '-2'), ('control: cents tie to even', ('2.665', 'USD'), '2.66'),
('control: yen tie', ('1235.5', 'JPY'), '1236'), ('control: dinar three places', ('10.0005', 'KWD'), '10.000')],
[('regression exact-decimal-input 1', ('41458.105', 'EUR'), '41458.10'),
('regression exact-decimal-input 2', ('24714.6255', 'KWD'), '24714.626'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00')]]
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 exact-decimal-input 1 | 2.68 | 2.68 | Passed |
| regression exact-decimal-input 2 | 0.12 | 0.13 | Failed |
| partial repair guard 2 | 1.000 | 1.001 | Failed |
| control: cents tie to even | 2.66 | 2.66 | Passed |
| control: yen tie | 1236 | 1236 | Passed |
| control: dinar three places | 10.000 | 10.000 | Passed |
| control: unit of account four places | 1.2346 | 1.2346 | Passed |
SHA-256 / 1ab4a12800be7a91bb5ae92649cbf5780c38d402eceb08f01082ff5be30442b3
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, ccy):
exps = {'USD': 2, 'EUR': 2, 'JPY': 0, 'KRW': 0, 'KWD': 3, 'BHD': 3, 'CLF': 4}
code = ccy.strip().upper()
if code not in exps: return 'ERR:unknown-currency'
e = exps[code]
x = Decimal(amount)
q = x.quantize(Decimal(1).scaleb(-e), rounding=ROUND_HALF_EVEN)
if q == 0: q = abs(q)
return format(q, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-decimal-input 1', ('2.675', 'USD'), '2.68'),
('regression exact-decimal-input 2', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: cents tie to even', ('2.665', 'USD'), '2.66'), ('control: yen tie', ('1235.5', 'JPY'), '1236'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346')],
[('regression exact-decimal-input 1', ('1.0005000000000000000001', 'KWD'), '1.001'),
('regression exact-decimal-input 2', ('2.665', 'USD'), '2.66'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00'),
('control: integer input', ('7', 'BHD'), '7.000')],
[('regression exact-decimal-input 1', ('10.0005', 'KWD'), '10.000'),
('regression exact-decimal-input 2', ('12.345', ' eur '), '12.34'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: integer input', ('7', 'BHD'), '7.000'), ('control: unknown code', ('1.00', 'ABC'), 'ERR:unknown-currency'),
('control: large amount', ('123456789.125', 'USD'), '123456789.12'),
('control: negative tie', ('-2.5', 'KRW'), '-2')],
[('regression exact-decimal-input 1', ('48525.1705', 'Kwd '), '48525.170'),
('regression exact-decimal-input 2', ('38174.12705', 'CLF'), '38174.1270'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: negative tie', ('-2.5', 'KRW'), '-2'), ('control: cents tie to even', ('2.665', 'USD'), '2.66'),
('control: yen tie', ('1235.5', 'JPY'), '1236'), ('control: dinar three places', ('10.0005', 'KWD'), '10.000')],
[('regression exact-decimal-input 1', ('41458.105', 'EUR'), '41458.10'),
('regression exact-decimal-input 2', ('24714.6255', 'KWD'), '24714.626'),
('partial repair guard 1', ('0.12500000000000000001', 'USD'), '0.13'),
('partial repair guard 2', ('1.0005000000000000000001', 'KWD'), '1.001'),
('control: dinar three places', ('10.0005', 'KWD'), '10.000'),
('control: unit of account four places', ('1.23456', 'CLF'), '1.2346'),
('control: lowercase code', ('12.345', ' eur '), '12.34'), ('control: negative small', ('-0.004', 'USD'), '0.00')]]
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 exact-decimal-input 1 | 2.68 | 2.68 | Passed |
| regression exact-decimal-input 2 | 0.13 | 0.13 | Passed |
| partial repair guard 2 | 1.001 | 1.001 | Passed |
| control: cents tie to even | 2.66 | 2.66 | Passed |
| control: yen tie | 1236 | 1236 | Passed |
| control: dinar three places | 10.000 | 10.000 | Passed |
| control: unit of account four places | 1.2346 | 1.2346 | Passed |
SHA-256 / 981132732fe1f9a3a221651af55231f2afd99f8578b8193788a67cd9e7ce2555
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:46:59.214743+00:00.
Case digest / 4617fb9744361214a5ce5a658b495fcc9b924bbea3ed6f9ed924a36ce34e33ca