FAILURE MAP
← Case archive

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

Yen prices receive a decimal .99 charm ending · case 01

Yen shelf prices appear as 1234.99.

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

ROOT CAUSE

The JPY branch is missing, so the decimal .99 rule is applied to a zero-decimal currency.

VERIFIED REPAIR

For JPY use the smallest price of the form 10k-2 not below raw.

Unsuccessful approach: Using ceil(raw/10)*10-2 without the bump prices values ending in 9 below raw.

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
        c = raw.to_integral_value(rounding=ROUND_FLOOR) + Decimal('0.99')
        if c < raw: c += 1
        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 yen-ending 1', ('1239', '1', 'JPY'), '1248'),
  ('regression yen-ending 2', ('1234.5', '1', 'JPY'), '1238'),
  ('partial repair guard 2', ('1408.999', '1', 'JPY'), '1418'),
  ('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'),
  ('control: yen exact ending', ('1238', '1', 'JPY'), '1238')],
 [('regression yen-ending 1', ('1238', '1', 'JPY'), '1238'),
  ('regression yen-ending 2', ('9.99', '151.37', 'JPY'), '1518'),
  ('partial repair guard 1', ('128.999', '1', 'JPY'), '138'), ('partial repair guard 2', ('538.99', '1', 'JPY'), '548'),
  ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
  ('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99')],
 [('regression yen-ending 1', ('1408.999', '1', 'JPY'), '1418'),
  ('regression yen-ending 2', ('73.04', '172.2043', 'JPY'), '12578'),
  ('partial repair guard 1', ('82.26', '4.0106', 'JPY'), '338'),
  ('partial repair guard 2', ('291.37', '7.8591', 'JPY'), '2298'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99'),
  ('control: sub-cent above', ('11.99004', '1', 'GBP'), '12.99'),
  ('control: yen converted', ('9.99', '151.37', 'JPY'), '1518')],
 [('regression yen-ending 1', ('114', '1', 'JPY'), '118'),
  ('regression yen-ending 2', ('42.57', '41.3869', 'JPY'), '1768'),
  ('partial repair guard 1', ('63.46', '153.7785', 'JPY'), '9768'),
  ('partial repair guard 2', ('2209.9996', '1', 'JPY'), '2218'),
  ('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 yen-ending 1', ('225.53', '87.4879', 'JPY'), '19738'),
  ('regression yen-ending 2', ('128.999', '1', 'JPY'), '138'),
  ('partial repair guard 1', ('124.55', '167.3895', 'JPY'), '20858'),
  ('partial repair guard 2', ('119', '1', 'JPY'), '128'), ('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 fixtureActualExpectedOutcome
regression yen-ending 11239.991248Failed
regression yen-ending 21234.991238Failed
partial repair guard 21409.991418Failed
control: whole number12.9912.99Passed
control: already charm11.9911.99Passed
control: above charm13.9913.99Passed
control: yen exact ending1238.991238Failed

SHA-256 / aae081d6408f353f1134d359cd3a46ffb75dcba1201cd9a8d909b97e5802c925

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
        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 yen-ending 1', ('1239', '1', 'JPY'), '1248'),
  ('regression yen-ending 2', ('1234.5', '1', 'JPY'), '1238'),
  ('partial repair guard 2', ('1408.999', '1', 'JPY'), '1418'),
  ('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'),
  ('control: yen exact ending', ('1238', '1', 'JPY'), '1238')],
 [('regression yen-ending 1', ('1238', '1', 'JPY'), '1238'),
  ('regression yen-ending 2', ('9.99', '151.37', 'JPY'), '1518'),
  ('partial repair guard 1', ('128.999', '1', 'JPY'), '138'), ('partial repair guard 2', ('538.99', '1', 'JPY'), '548'),
  ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
  ('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99')],
 [('regression yen-ending 1', ('1408.999', '1', 'JPY'), '1418'),
  ('regression yen-ending 2', ('73.04', '172.2043', 'JPY'), '12578'),
  ('partial repair guard 1', ('82.26', '4.0106', 'JPY'), '338'),
  ('partial repair guard 2', ('291.37', '7.8591', 'JPY'), '2298'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99'),
  ('control: sub-cent above', ('11.99004', '1', 'GBP'), '12.99'),
  ('control: yen converted', ('9.99', '151.37', 'JPY'), '1518')],
 [('regression yen-ending 1', ('114', '1', 'JPY'), '118'),
  ('regression yen-ending 2', ('42.57', '41.3869', 'JPY'), '1768'),
  ('partial repair guard 1', ('63.46', '153.7785', 'JPY'), '9768'),
  ('partial repair guard 2', ('2209.9996', '1', 'JPY'), '2218'),
  ('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 yen-ending 1', ('225.53', '87.4879', 'JPY'), '19738'),
  ('regression yen-ending 2', ('128.999', '1', 'JPY'), '138'),
  ('partial repair guard 1', ('124.55', '167.3895', 'JPY'), '20858'),
  ('partial repair guard 2', ('119', '1', 'JPY'), '128'), ('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 fixtureActualExpectedOutcome
regression yen-ending 112381248Failed
regression yen-ending 212381238Passed
partial repair guard 214081418Failed
control: whole number12.9912.99Passed
control: already charm11.9911.99Passed
control: above charm13.9913.99Passed
control: yen exact ending12381238Passed

SHA-256 / 1b91f9b6137539e100c211dc0aeb4710bcb78d26897ef351b45e27f78b267283

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 yen-ending 1', ('1239', '1', 'JPY'), '1248'),
  ('regression yen-ending 2', ('1234.5', '1', 'JPY'), '1238'),
  ('partial repair guard 2', ('1408.999', '1', 'JPY'), '1418'),
  ('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'),
  ('control: yen exact ending', ('1238', '1', 'JPY'), '1238')],
 [('regression yen-ending 1', ('1238', '1', 'JPY'), '1238'),
  ('regression yen-ending 2', ('9.99', '151.37', 'JPY'), '1518'),
  ('partial repair guard 1', ('128.999', '1', 'JPY'), '138'), ('partial repair guard 2', ('538.99', '1', 'JPY'), '548'),
  ('control: yen round up', ('1234.5', '1', 'JPY'), '1238'),
  ('control: yen above ending', ('1239', '1', 'JPY'), '1248'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99')],
 [('regression yen-ending 1', ('1408.999', '1', 'JPY'), '1418'),
  ('regression yen-ending 2', ('73.04', '172.2043', 'JPY'), '12578'),
  ('partial repair guard 1', ('82.26', '4.0106', 'JPY'), '338'),
  ('partial repair guard 2', ('291.37', '7.8591', 'JPY'), '2298'),
  ('control: converted euro', ('10.00', '0.92', 'EUR'), '9.99'),
  ('control: just above charm', ('11.994', '1', 'USD'), '12.99'),
  ('control: sub-cent above', ('11.99004', '1', 'GBP'), '12.99'),
  ('control: yen converted', ('9.99', '151.37', 'JPY'), '1518')],
 [('regression yen-ending 1', ('114', '1', 'JPY'), '118'),
  ('regression yen-ending 2', ('42.57', '41.3869', 'JPY'), '1768'),
  ('partial repair guard 1', ('63.46', '153.7785', 'JPY'), '9768'),
  ('partial repair guard 2', ('2209.9996', '1', 'JPY'), '2218'),
  ('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 yen-ending 1', ('225.53', '87.4879', 'JPY'), '19738'),
  ('regression yen-ending 2', ('128.999', '1', 'JPY'), '138'),
  ('partial repair guard 1', ('124.55', '167.3895', 'JPY'), '20858'),
  ('partial repair guard 2', ('119', '1', 'JPY'), '128'), ('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 fixtureActualExpectedOutcome
regression yen-ending 112481248Passed
regression yen-ending 212381238Passed
partial repair guard 214181418Passed
control: whole number12.9912.99Passed
control: already charm11.9911.99Passed
control: above charm13.9913.99Passed
control: yen exact ending12381238Passed

SHA-256 / f2d85699ca074b8e356cc143c6ae8080e10bc18f6d032562e3dc22f9bef29844

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

Case digest / e014c2756d112650ecdc9eb5767f2ca93d88ed6aa09082ce60b628d6b57c468d