FAILURE MAP
← Case archive

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

A conversion into EUR returns the three-decimal intermediate · case 01

Marks converted to euros are reported with three decimals.

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

ROOT CAUSE

The legacy-to-EUR path is routed through the three-decimal triangulation intermediate.

VERIFIED REPAIR

Legacy to EUR divides by the rate and rounds once to two decimals.

Unsuccessful approach: Rounding the three-decimal intermediate again to cents double-rounds values like x.xx45.

Case contract

solve(amount, frm, to): stipulated legacy-currency triangulation. Fixed rates are units per 1 EUR (DEM 1.95583, FRF 6.55957, ITL 1936.27, ESP 166.386, IEP 0.787564, NLG 2.20371, BEF 40.3399, PTE 200.482, ATS 13.7603, FIM 5.94573). Exponents: ITL, ESP, BEF, PTE 0; others and EUR 2. Legacy to legacy: divide by the source rate, round the EUR intermediate half-up to 3 decimals, multiply by the target rate, round half-up to the target exponent. Legacy to EUR: divide and round half-up to 2 decimals. EUR to legacy: multiply and round half-up to the target exponent. Same currency: round the amount half-up to its exponent. Inverse rates are never used. Unknown codes return 'ERR: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, frm, to):
    rates = {'DEM': '1.95583', 'FRF': '6.55957', 'ITL': '1936.27', 'ESP': '166.386', 'IEP': '0.787564', 'NLG': '2.20371', 'BEF': '40.3399', 'PTE': '200.482', 'ATS': '13.7603', 'FIM': '5.94573', 'EUR': '1'}
    zero = ('ITL', 'ESP', 'BEF', 'PTE')
    if frm not in rates or to not in rates: return 'ERR:currency'
    def q(v, c):
        return v.quantize(Decimal(1) if c in zero else Decimal('0.01'), rounding=ROUND_HALF_UP)
    x = Decimal(amount)
    if frm == to: return format(q(x, to), 'f')
    if frm == 'EUR':
        eur = x
    elif to == 'EUR':
        return format((x / Decimal(rates[frm])).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP), 'f')
    else:
        eur = (x / Decimal(rates[frm])).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP)
    return format(q(eur * Decimal(rates[to]), to), 'f')
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression euro-target-rounding 1', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('regression euro-target-rounding 2', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('regression euro-target-rounding 2', ('106', 'ITL', 'EUR'), '0.05'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')],
 [('regression euro-target-rounding 1', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('regression euro-target-rounding 2', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('partial repair guard 2', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1'),
  ('control: schilling to markka', ('987.65', 'ATS', 'FIM'), '426.75'),
  ('control: franc to franc', ('12.345', 'BEF', 'FRF'), '2.01'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38')],
 [('regression euro-target-rounding 1', ('2838484', 'ESP', 'EUR'), '17059.63'),
  ('regression euro-target-rounding 2', ('4062865.64', 'FRF', 'EUR'), '619379.87'),
  ('partial repair guard 1', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('partial repair guard 2', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('4466382', 'FRF', 'EUR'), '680895.55'),
  ('regression euro-target-rounding 2', ('3694885', 'IEP', 'EUR'), '4691536.18'),
  ('partial repair guard 1', ('106', 'ITL', 'EUR'), '0.05'), ('partial repair guard 2', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')]]
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 euro-target-rounding 10.4350.43Failed
regression euro-target-rounding 21.1151.11Failed
control: mark to franc335.38335.38Passed
control: lira to euro516.457516.46Failed
control: euro to lira1936319363Passed
control: same currency rounding101101Passed

SHA-256 / ab1806dddecd653cdd502f334eab861cfe956674c062ab7078a1155f645cb59a

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, frm, to):
    rates = {'DEM': '1.95583', 'FRF': '6.55957', 'ITL': '1936.27', 'ESP': '166.386', 'IEP': '0.787564', 'NLG': '2.20371', 'BEF': '40.3399', 'PTE': '200.482', 'ATS': '13.7603', 'FIM': '5.94573', 'EUR': '1'}
    zero = ('ITL', 'ESP', 'BEF', 'PTE')
    if frm not in rates or to not in rates: return 'ERR:currency'
    def q(v, c):
        return v.quantize(Decimal(1) if c in zero else Decimal('0.01'), rounding=ROUND_HALF_UP)
    x = Decimal(amount)
    if frm == to: return format(q(x, to), 'f')
    if frm == 'EUR':
        eur = x
    elif to == 'EUR':
        return format(q((x / Decimal(rates[frm])).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP), 'EUR'), 'f')
    else:
        eur = (x / Decimal(rates[frm])).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP)
    return format(q(eur * Decimal(rates[to]), to), 'f')
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression euro-target-rounding 1', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('regression euro-target-rounding 2', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('regression euro-target-rounding 2', ('106', 'ITL', 'EUR'), '0.05'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')],
 [('regression euro-target-rounding 1', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('regression euro-target-rounding 2', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('partial repair guard 2', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1'),
  ('control: schilling to markka', ('987.65', 'ATS', 'FIM'), '426.75'),
  ('control: franc to franc', ('12.345', 'BEF', 'FRF'), '2.01'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38')],
 [('regression euro-target-rounding 1', ('2838484', 'ESP', 'EUR'), '17059.63'),
  ('regression euro-target-rounding 2', ('4062865.64', 'FRF', 'EUR'), '619379.87'),
  ('partial repair guard 1', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('partial repair guard 2', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('4466382', 'FRF', 'EUR'), '680895.55'),
  ('regression euro-target-rounding 2', ('3694885', 'IEP', 'EUR'), '4691536.18'),
  ('partial repair guard 1', ('106', 'ITL', 'EUR'), '0.05'), ('partial repair guard 2', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')]]
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 euro-target-rounding 10.440.43Failed
regression euro-target-rounding 21.121.11Failed
control: mark to franc335.38335.38Passed
control: lira to euro516.46516.46Passed
control: euro to lira1936319363Passed
control: same currency rounding101101Passed

SHA-256 / 30fd11f9d651cf659c0df1dfd2ec1e1bf4629c9332db799c9c0f27ea0e49377e

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, frm, to):
    rates = {'DEM': '1.95583', 'FRF': '6.55957', 'ITL': '1936.27', 'ESP': '166.386', 'IEP': '0.787564', 'NLG': '2.20371', 'BEF': '40.3399', 'PTE': '200.482', 'ATS': '13.7603', 'FIM': '5.94573', 'EUR': '1'}
    zero = ('ITL', 'ESP', 'BEF', 'PTE')
    if frm not in rates or to not in rates: return 'ERR:currency'
    def q(v, c):
        return v.quantize(Decimal(1) if c in zero else Decimal('0.01'), rounding=ROUND_HALF_UP)
    x = Decimal(amount)
    if frm == to: return format(q(x, to), 'f')
    if frm == 'EUR':
        eur = x
    elif to == 'EUR':
        return format(q(x / Decimal(rates[frm]), 'EUR'), 'f')
    else:
        eur = (x / Decimal(rates[frm])).quantize(Decimal('0.001'), rounding=ROUND_HALF_UP)
    return format(q(eur * Decimal(rates[to]), to), 'f')
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression euro-target-rounding 1', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('regression euro-target-rounding 2', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('regression euro-target-rounding 2', ('106', 'ITL', 'EUR'), '0.05'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')],
 [('regression euro-target-rounding 1', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('regression euro-target-rounding 2', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('partial repair guard 2', ('0.85', 'DEM', 'EUR'), '0.43'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1'),
  ('control: schilling to markka', ('987.65', 'ATS', 'FIM'), '426.75'),
  ('control: franc to franc', ('12.345', 'BEF', 'FRF'), '2.01'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38')],
 [('regression euro-target-rounding 1', ('2838484', 'ESP', 'EUR'), '17059.63'),
  ('regression euro-target-rounding 2', ('4062865.64', 'FRF', 'EUR'), '619379.87'),
  ('partial repair guard 1', ('2.18', 'DEM', 'EUR'), '1.11'),
  ('partial repair guard 2', ('5.54', 'FRF', 'EUR'), '0.84'),
  ('control: mark to franc', ('100', 'DEM', 'FRF'), '335.38'),
  ('control: lira to euro', ('1000000', 'ITL', 'EUR'), '516.46'),
  ('control: euro to lira', ('10', 'EUR', 'ITL'), '19363'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101')],
 [('regression euro-target-rounding 1', ('4466382', 'FRF', 'EUR'), '680895.55'),
  ('regression euro-target-rounding 2', ('3694885', 'IEP', 'EUR'), '4691536.18'),
  ('partial repair guard 1', ('106', 'ITL', 'EUR'), '0.05'), ('partial repair guard 2', ('1.20', 'NLG', 'EUR'), '0.54'),
  ('control: same currency rounding', ('100.5', 'ITL', 'ITL'), '101'),
  ('control: unknown', ('5', 'XEU', 'DEM'), 'ERR:currency'),
  ('control: peseta to punt', ('123456', 'ESP', 'IEP'), '584.36'),
  ('control: guilder to escudo', ('0.01', 'NLG', 'PTE'), '1')]]
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 euro-target-rounding 10.430.43Passed
regression euro-target-rounding 21.111.11Passed
control: mark to franc335.38335.38Passed
control: lira to euro516.46516.46Passed
control: euro to lira1936319363Passed
control: same currency rounding101101Passed

SHA-256 / 56b0271fd2d701a7a9d60c6f5af2048d0037e3b6866186dbc8f95a1dc8893955

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.023405+00:00.

Case digest / e9bacdbb89925ef391e0914f31eddb58db64592fc8c7440850cc6587f024704b