FA-62306 / Currency rounding and FX conversion / Open access
A converted value already on a charm price is bumped to the next one · case 01
11.99 converts to 12.99 even though 11.99 is already a valid charm price.
ROOT CAUSE
The bump condition uses c <= raw.
VERIFIED REPAIR
Only bump when the candidate is strictly below the exact value.
Unsuccessful approach: Comparing against raw rounded to cents lets sub-cent excess over a charm price through.
Case contract
solve(price, rate, ccy): a price list converted at rate is re-priced to the smallest charm price not below the exact converted value raw = price*rate. For JPY, charm prices end in 8 (…8 yen, i.e. 10k-2). For other currencies, charm prices end in .99. Return the charm price as a plain string.
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(price, rate, ccy):
raw = Decimal(price) * Decimal(rate)
if ccy == 'JPY':
c = (raw / 10).to_integral_value(rounding=ROUND_CEILING) * 10 - 2
if c < raw: c += 10
return format(c, 'f')
c = raw.to_integral_value(rounding=ROUND_FLOOR) + Decimal('0.99')
if c <= raw: c += 1
return format(c, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-charm-kept 1', ('11.99', '1', 'EUR'), '11.99'),
('regression exact-charm-kept 2', ('132.99', '1', 'GBP'), '132.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: above charm', ('12.995', '1', 'EUR'), '13.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248')],
[('regression exact-charm-kept 1', ('2759.99', '1', 'USD'), '2759.99'),
('regression exact-charm-kept 2', ('2699.99', '1', 'GBP'), '2699.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99')],
[('regression exact-charm-kept 1', ('2772.99', '1', 'EUR'), '2772.99'),
('regression exact-charm-kept 2', ('632.99', '1', 'USD'), '632.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'),
('control: already charm', ('11.99', '1', 'EUR'), '11.99')],
[('regression exact-charm-kept 1', ('1442.99', '1', 'USD'), '1442.99'),
('regression exact-charm-kept 2', ('1492.99', '1', 'EUR'), '1492.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: already charm', ('11.99', '1', 'EUR'), '11.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99')],
[('regression exact-charm-kept 1', ('1302.99', '1', 'USD'), '1302.99'),
('regression exact-charm-kept 2', ('788.99', '1', 'GBP'), '788.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99'), ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238')]]
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-charm-kept 1 | 12.99 | 11.99 | Failed |
| regression exact-charm-kept 2 | 133.99 | 132.99 | Failed |
| partial repair guard 1 | 12.99 | 12.99 | Passed |
| partial repair guard 2 | 12.99 | 12.99 | Passed |
| control: whole number | 12.99 | 12.99 | Passed |
| control: above charm | 13.99 | 13.99 | Passed |
| control: yen round up | 1238 | 1238 | Passed |
| control: yen above ending | 1248 | 1248 | Passed |
SHA-256 / 951b3ce6db4e8caea4343c1b4042c95eea6acc5daf7306ed13ad9424d004859b
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(price, rate, ccy):
raw = Decimal(price) * Decimal(rate)
if ccy == 'JPY':
c = (raw / 10).to_integral_value(rounding=ROUND_CEILING) * 10 - 2
if c < raw: c += 10
return format(c, 'f')
c = raw.to_integral_value(rounding=ROUND_FLOOR) + Decimal('0.99')
if c < raw.quantize(Decimal('0.01'), rounding=ROUND_HALF_EVEN): c += 1
return format(c, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-charm-kept 1', ('11.99', '1', 'EUR'), '11.99'),
('regression exact-charm-kept 2', ('132.99', '1', 'GBP'), '132.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: above charm', ('12.995', '1', 'EUR'), '13.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248')],
[('regression exact-charm-kept 1', ('2759.99', '1', 'USD'), '2759.99'),
('regression exact-charm-kept 2', ('2699.99', '1', 'GBP'), '2699.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99')],
[('regression exact-charm-kept 1', ('2772.99', '1', 'EUR'), '2772.99'),
('regression exact-charm-kept 2', ('632.99', '1', 'USD'), '632.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'),
('control: already charm', ('11.99', '1', 'EUR'), '11.99')],
[('regression exact-charm-kept 1', ('1442.99', '1', 'USD'), '1442.99'),
('regression exact-charm-kept 2', ('1492.99', '1', 'EUR'), '1492.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: already charm', ('11.99', '1', 'EUR'), '11.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99')],
[('regression exact-charm-kept 1', ('1302.99', '1', 'USD'), '1302.99'),
('regression exact-charm-kept 2', ('788.99', '1', 'GBP'), '788.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99'), ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238')]]
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-charm-kept 1 | 11.99 | 11.99 | Passed |
| regression exact-charm-kept 2 | 132.99 | 132.99 | Passed |
| partial repair guard 1 | 11.99 | 12.99 | Failed |
| partial repair guard 2 | 11.99 | 12.99 | Failed |
| control: whole number | 12.99 | 12.99 | Passed |
| control: above charm | 13.99 | 13.99 | Passed |
| control: yen round up | 1238 | 1238 | Passed |
| control: yen above ending | 1248 | 1248 | Passed |
SHA-256 / 13bf911d30ab65725e3e64c904888f7dd46a802f3ae6442489c9f9d00e488c3f
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(price, rate, ccy):
raw = Decimal(price) * Decimal(rate)
if ccy == 'JPY':
c = (raw / 10).to_integral_value(rounding=ROUND_CEILING) * 10 - 2
if c < raw: c += 10
return format(c, 'f')
c = raw.to_integral_value(rounding=ROUND_FLOOR) + Decimal('0.99')
if c < raw: c += 1
return format(c, 'f')
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression exact-charm-kept 1', ('11.99', '1', 'EUR'), '11.99'),
('regression exact-charm-kept 2', ('132.99', '1', 'GBP'), '132.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: above charm', ('12.995', '1', 'EUR'), '13.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248')],
[('regression exact-charm-kept 1', ('2759.99', '1', 'USD'), '2759.99'),
('regression exact-charm-kept 2', ('2699.99', '1', 'GBP'), '2699.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99')],
[('regression exact-charm-kept 1', ('2772.99', '1', 'EUR'), '2772.99'),
('regression exact-charm-kept 2', ('632.99', '1', 'USD'), '632.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'),
('control: already charm', ('11.99', '1', 'EUR'), '11.99')],
[('regression exact-charm-kept 1', ('1442.99', '1', 'USD'), '1442.99'),
('regression exact-charm-kept 2', ('1492.99', '1', 'EUR'), '1492.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: yen converted', ('9.99', '151.37', 'JPY'), '1518'),
('control: whole number', ('12.00', '1', 'EUR'), '12.99'), ('control: already charm', ('11.99', '1', 'EUR'), '11.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99')],
[('regression exact-charm-kept 1', ('1302.99', '1', 'USD'), '1302.99'),
('regression exact-charm-kept 2', ('788.99', '1', 'GBP'), '788.99'),
('partial repair guard 1', ('11.994', '1', 'USD'), '12.99'),
('partial repair guard 2', ('11.99004', '1', 'GBP'), '12.99'),
('control: above charm', ('12.995', '1', 'EUR'), '13.99'), ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
('control: yen exact ending', ('1238', '1', 'JPY'), '1238')]]
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-charm-kept 1 | 11.99 | 11.99 | Passed |
| regression exact-charm-kept 2 | 132.99 | 132.99 | Passed |
| partial repair guard 1 | 12.99 | 12.99 | Passed |
| partial repair guard 2 | 12.99 | 12.99 | Passed |
| control: whole number | 12.99 | 12.99 | Passed |
| control: above charm | 13.99 | 13.99 | Passed |
| control: yen round up | 1238 | 1238 | Passed |
| control: yen above ending | 1248 | 1248 | Passed |
SHA-256 / 66c05e85718be18a52a16cc2fa1b19171958df4b519568c638c749d0ae0045c0
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:03.369890+00:00.
Case digest / 56b7c31da356f13659cce2998e666c16e1b2976d234394a7682d5258462f94ef