FA-62186 / Currency rounding and FX conversion / Open access
The disclosed markup is measured against the offered rate · case 01
A 3% markup is disclosed as 2.92%.
ROOT CAUSE
The disclosure divides the rate difference by the offered rate instead of the reference rate.
VERIFIED REPAIR
Disclose (offered/ref - 1)*100 using the rounded offered rate.
Unsuccessful approach: Disclosing bps/100 ignores the effect of rounding the offered rate.
Case contract
solve(amount, ref_rate, markup_bps, card_exp): amount in merchant currency, ref_rate cardholder units per merchant unit, markup_bps an integer (negative for a promotional discount). The offered rate is ref*(1+bps/10000) rounded half-up to 6 decimals, and the card amount is amount*offered rounded half-up at card_exp. The disclosed markup percentage is (offered/ref - 1)*100 computed from the rounded offered rate and rounded toward positive infinity to 2 decimals so it never understates. Return [offered, card_amount, markup_pct] as strings.
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, ref_rate, markup_bps, card_exp):
ref = Decimal(ref_rate)
raw = ref * (1 + Decimal(markup_bps) / 10000)
dcc = raw.quantize(Decimal('0.000001'), rounding=ROUND_HALF_UP)
card = (Decimal(amount) * dcc).quantize(Decimal(1).scaleb(-card_exp), rounding=ROUND_HALF_UP)
pct = ((dcc - ref) / dcc * 100).quantize(Decimal('0.01'), rounding=ROUND_CEILING)
return [format(dcc, 'f'), format(card, 'f'), format(pct, 'f')]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression markup-disclosure-base 1', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('regression markup-disclosure-base 2', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('partial repair guard 2', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00'])],
[('regression markup-disclosure-base 1', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('regression markup-disclosure-base 2', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('partial repair guard 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('partial repair guard 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51'])],
[('regression markup-disclosure-base 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('regression markup-disclosure-base 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('partial repair guard 1', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 2', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00'])],
[('regression markup-disclosure-base 1', ('3884.23', '183.00464', 350, 0), ['189.409802', '735711', '3.50']),
('regression markup-disclosure-base 2', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 1', ('508.07', '12.046349', 275, 0), ['12.377624', '6289', '2.76']),
('partial repair guard 2', ('3826.89', '1.5416740', -50, 2), ['1.533966', '5870.32', '-0.49']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00'])],
[('regression markup-disclosure-base 1', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('regression markup-disclosure-base 2', ('3408.26', '18.286201', 150, 2), ['18.560494', '63258.99', '1.50']),
('partial repair guard 1', ('419.33', '78.12165', 350, 2), ['80.855908', '33905.31', '3.51']),
('partial repair guard 2', ('2378.17', '122.42736', 300, 2), ['126.100181', '299887.67', '3.01']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.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 markup-disclosure-base 1 | ['1.115975', '111.60', '2.92'] | ['1.115975', '111.60', '3.00'] | Failed |
| regression markup-disclosure-base 2 | ['0.006852', '34.26', '3.39'] | ['0.006852', '34.26', '3.51'] | Failed |
| partial repair guard 2 | ['155.536991', '7775', '2.68'] | ['155.536991', '7775', '2.76'] | Failed |
| control: dinar card | ['0.319418', '79.855', '3.85'] | ['0.319418', '79.855', '4.00'] | Failed |
| control: promotional discount | ['0.908810', '72.70', '-0.50'] | ['0.908810', '72.70', '-0.50'] | Passed |
| control: zero markup | ['1.200000', '14.81', '0.00'] | ['1.200000', '14.81', '0.00'] | Passed |
SHA-256 / 1a60cb4e8bddc04dc932513915314dd84d8b0300629845554c41287522d4ddb5
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, ref_rate, markup_bps, card_exp):
ref = Decimal(ref_rate)
raw = ref * (1 + Decimal(markup_bps) / 10000)
dcc = raw.quantize(Decimal('0.000001'), rounding=ROUND_HALF_UP)
card = (Decimal(amount) * dcc).quantize(Decimal(1).scaleb(-card_exp), rounding=ROUND_HALF_UP)
pct = (Decimal(markup_bps) / 100).quantize(Decimal('0.01'), rounding=ROUND_CEILING)
return [format(dcc, 'f'), format(card, 'f'), format(pct, 'f')]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression markup-disclosure-base 1', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('regression markup-disclosure-base 2', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('partial repair guard 2', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00'])],
[('regression markup-disclosure-base 1', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('regression markup-disclosure-base 2', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('partial repair guard 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('partial repair guard 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51'])],
[('regression markup-disclosure-base 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('regression markup-disclosure-base 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('partial repair guard 1', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 2', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00'])],
[('regression markup-disclosure-base 1', ('3884.23', '183.00464', 350, 0), ['189.409802', '735711', '3.50']),
('regression markup-disclosure-base 2', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 1', ('508.07', '12.046349', 275, 0), ['12.377624', '6289', '2.76']),
('partial repair guard 2', ('3826.89', '1.5416740', -50, 2), ['1.533966', '5870.32', '-0.49']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00'])],
[('regression markup-disclosure-base 1', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('regression markup-disclosure-base 2', ('3408.26', '18.286201', 150, 2), ['18.560494', '63258.99', '1.50']),
('partial repair guard 1', ('419.33', '78.12165', 350, 2), ['80.855908', '33905.31', '3.51']),
('partial repair guard 2', ('2378.17', '122.42736', 300, 2), ['126.100181', '299887.67', '3.01']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.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 markup-disclosure-base 1 | ['1.115975', '111.60', '3.00'] | ['1.115975', '111.60', '3.00'] | Passed |
| regression markup-disclosure-base 2 | ['0.006852', '34.26', '3.50'] | ['0.006852', '34.26', '3.51'] | Failed |
| partial repair guard 2 | ['155.536991', '7775', '2.75'] | ['155.536991', '7775', '2.76'] | Failed |
| control: dinar card | ['0.319418', '79.855', '4.00'] | ['0.319418', '79.855', '4.00'] | Passed |
| control: promotional discount | ['0.908810', '72.70', '-0.50'] | ['0.908810', '72.70', '-0.50'] | Passed |
| control: zero markup | ['1.200000', '14.81', '0.00'] | ['1.200000', '14.81', '0.00'] | Passed |
SHA-256 / 02597a936818070cdab10313ac8617d0be26bceb34120b3026595d4a4b31837b
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, ref_rate, markup_bps, card_exp):
ref = Decimal(ref_rate)
raw = ref * (1 + Decimal(markup_bps) / 10000)
dcc = raw.quantize(Decimal('0.000001'), rounding=ROUND_HALF_UP)
card = (Decimal(amount) * dcc).quantize(Decimal(1).scaleb(-card_exp), rounding=ROUND_HALF_UP)
pct = ((dcc / ref - 1) * 100).quantize(Decimal('0.01'), rounding=ROUND_CEILING)
return [format(dcc, 'f'), format(card, 'f'), format(pct, 'f')]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression markup-disclosure-base 1', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('regression markup-disclosure-base 2', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('partial repair guard 2', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00'])],
[('regression markup-disclosure-base 1', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('regression markup-disclosure-base 2', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('partial repair guard 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('partial repair guard 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51'])],
[('regression markup-disclosure-base 1', ('3353.43', '37.36650', 275, 0), ['38.394079', '128752', '2.76']),
('regression markup-disclosure-base 2', ('1171.26', '0.4683115', -50, 3), ['0.465970', '545.772', '-0.49']),
('partial repair guard 1', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 2', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00'])],
[('regression markup-disclosure-base 1', ('3884.23', '183.00464', 350, 0), ['189.409802', '735711', '3.50']),
('regression markup-disclosure-base 2', ('157.22', '31.44890', 150, 3), ['31.920634', '5018.562', '1.51']),
('partial repair guard 1', ('508.07', '12.046349', 275, 0), ['12.377624', '6289', '2.76']),
('partial repair guard 2', ('3826.89', '1.5416740', -50, 2), ['1.533966', '5870.32', '-0.49']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.00']),
('control: promotional discount', ('80.00', '0.913377', -50, 2), ['0.908810', '72.70', '-0.50']),
('control: zero markup', ('12.34', '1.2', 0, 2), ['1.200000', '14.81', '0.00']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00'])],
[('regression markup-disclosure-base 1', ('3157.31', '0.0891399', 150, 3), ['0.090477', '285.664', '1.51']),
('regression markup-disclosure-base 2', ('3408.26', '18.286201', 150, 2), ['18.560494', '63258.99', '1.50']),
('partial repair guard 1', ('419.33', '78.12165', 350, 2), ['80.855908', '33905.31', '3.51']),
('partial repair guard 2', ('2378.17', '122.42736', 300, 2), ['126.100181', '299887.67', '3.01']),
('control: euro to dollar', ('100.00', '1.083471', 300, 2), ['1.115975', '111.60', '3.00']),
('control: yen to dollar', ('5000', '0.00662', 350, 2), ['0.006852', '34.26', '3.51']),
('control: to yen card', ('49.99', '151.3742', 275, 0), ['155.536991', '7775', '2.76']),
('control: dinar card', ('250.00', '0.307133', 400, 3), ['0.319418', '79.855', '4.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 markup-disclosure-base 1 | ['1.115975', '111.60', '3.00'] | ['1.115975', '111.60', '3.00'] | Passed |
| regression markup-disclosure-base 2 | ['0.006852', '34.26', '3.51'] | ['0.006852', '34.26', '3.51'] | Passed |
| partial repair guard 2 | ['155.536991', '7775', '2.76'] | ['155.536991', '7775', '2.76'] | Passed |
| control: dinar card | ['0.319418', '79.855', '4.00'] | ['0.319418', '79.855', '4.00'] | Passed |
| control: promotional discount | ['0.908810', '72.70', '-0.50'] | ['0.908810', '72.70', '-0.50'] | Passed |
| control: zero markup | ['1.200000', '14.81', '0.00'] | ['1.200000', '14.81', '0.00'] | Passed |
SHA-256 / e3eb2b9a5280501fc53d61bbacc448f34101395525f37605e0e74e02a4f4c6a2
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:02.317626+00:00.
Case digest / cd7c618ecb836551104d3b6847060e3eb54f439ec557bfd1c411c0ff36d756d9