FAILURE MAP
← Case archive

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

Old minor digits are dropped before applying the redenomination factor · case 01

Legacy amounts with fractional old units lose value when redenominated.

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

ROOT CAUSE

The amount is truncated to whole old units before dividing by the factor.

VERIFIED REPAIR

Divide the full old minor amount by the factor.

Unsuccessful approach: Truncating whole old units while restoring the new exponent still discards the old minor digits.

Case contract

solve(amount_minor, date, cutover, factor, old_exp, new_exp): amounts dated on or after the cutover (ISO date strings) are already in the new currency and returned unchanged as [amount_minor, 'native']. Earlier amounts are old-currency minor units: new = amount_minor / factor * 10**(new_exp - old_exp), rounded half away from zero, returned as [new, 'converted']. factor is old units per new unit.

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_minor, date, cutover, factor, old_exp, new_exp):
    if date >= cutover: return [amount_minor, 'native']
    v = Decimal(amount_minor * factor) * Decimal(10) ** (new_exp - old_exp)
    return [int(v.quantize(Decimal(1), rounding=ROUND_HALF_UP)), 'converted']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 100, 2, 2), [50, 'converted']),
  ('regression old-minor-digits 2', (1234567, '2026-06-30', '2026-07-01', 10, 2, 2), [123457, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted'])],
 [('regression old-minor-digits 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('regression old-minor-digits 2', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('partial repair guard 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('regression old-minor-digits 2', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('partial repair guard 2', (22923087, '2026-05-16', '2026-07-15', 100, 2, 3), [2292309, 'converted']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('regression old-minor-digits 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('partial repair guard 1', (5928374, '2026-05-04', '2026-07-20', 1000, 2, 3), [59284, 'converted']),
  ('partial repair guard 2', (26062705, '2026-05-21', '2026-07-20', 100, 2, 3), [2606271, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: same month earlier', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native'])],
 [('regression old-minor-digits 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('regression old-minor-digits 2', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
  ('partial repair guard 1', (29809251, '2026-05-01', '2026-07-15', 1000, 2, 3), [298093, 'converted']),
  ('partial repair guard 2', (-2819546, '2026-05-21', '2026-07-20', 1000, 2, 3), [-28195, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])]]
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 old-minor-digits 1[499900, 'converted'][50, 'converted']Failed
regression old-minor-digits 2[12345670, 'converted'][123457, 'converted']Failed
control: before cutover[12345670000, 'converted'][123, 'converted']Failed
control: negative tie[-150000000, 'converted'][-2, 'converted']Failed
control: cutover day[500, 'native'][500, 'native']Passed
control: exponent gain[123456700000, 'converted'][123457, 'converted']Failed

SHA-256 / 332c535906e0651fd214d3f545e1f5147e4660076ab7431b5a22f00441b2f305

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_minor, date, cutover, factor, old_exp, new_exp):
    if date >= cutover: return [amount_minor, 'native']
    v = Decimal(amount_minor // 10 ** old_exp) / factor * Decimal(10) ** new_exp
    return [int(v.quantize(Decimal(1), rounding=ROUND_HALF_UP)), 'converted']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 100, 2, 2), [50, 'converted']),
  ('regression old-minor-digits 2', (1234567, '2026-06-30', '2026-07-01', 10, 2, 2), [123457, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted'])],
 [('regression old-minor-digits 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('regression old-minor-digits 2', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('partial repair guard 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('regression old-minor-digits 2', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('partial repair guard 2', (22923087, '2026-05-16', '2026-07-15', 100, 2, 3), [2292309, 'converted']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('regression old-minor-digits 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('partial repair guard 1', (5928374, '2026-05-04', '2026-07-20', 1000, 2, 3), [59284, 'converted']),
  ('partial repair guard 2', (26062705, '2026-05-21', '2026-07-20', 100, 2, 3), [2606271, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: same month earlier', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native'])],
 [('regression old-minor-digits 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('regression old-minor-digits 2', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
  ('partial repair guard 1', (29809251, '2026-05-01', '2026-07-15', 1000, 2, 3), [298093, 'converted']),
  ('partial repair guard 2', (-2819546, '2026-05-21', '2026-07-20', 1000, 2, 3), [-28195, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])]]
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 old-minor-digits 1[49, 'converted'][50, 'converted']Failed
regression old-minor-digits 2[123450, 'converted'][123457, 'converted']Failed
control: before cutover[123, 'converted'][123, 'converted']Passed
control: negative tie[-2, 'converted'][-2, 'converted']Passed
control: cutover day[500, 'native'][500, 'native']Passed
control: exponent gain[123457, 'converted'][123457, 'converted']Passed

SHA-256 / 5210dd5ba2a0ef54a5ebe1343f130472e53ad8f4eb9005f1f9648fa2d62b2a0c

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_minor, date, cutover, factor, old_exp, new_exp):
    if date >= cutover: return [amount_minor, 'native']
    v = Decimal(amount_minor) / factor * Decimal(10) ** (new_exp - old_exp)
    return [int(v.quantize(Decimal(1), rounding=ROUND_HALF_UP)), 'converted']
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 100, 2, 2), [50, 'converted']),
  ('regression old-minor-digits 2', (1234567, '2026-06-30', '2026-07-01', 10, 2, 2), [123457, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted'])],
 [('regression old-minor-digits 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('regression old-minor-digits 2', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('partial repair guard 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('regression old-minor-digits 2', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('partial repair guard 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('partial repair guard 2', (22923087, '2026-05-16', '2026-07-15', 100, 2, 3), [2292309, 'converted']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native'])],
 [('regression old-minor-digits 1', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('regression old-minor-digits 2', (-2744763, '2026-06-01', '2026-07-01', 100, 2, 3), [-274476, 'converted']),
  ('partial repair guard 1', (5928374, '2026-05-04', '2026-07-20', 1000, 2, 3), [59284, 'converted']),
  ('partial repair guard 2', (26062705, '2026-05-21', '2026-07-20', 100, 2, 3), [2606271, 'converted']),
  ('control: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
  ('control: exponent gain', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
  ('control: same month earlier', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native'])],
 [('regression old-minor-digits 1', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
  ('regression old-minor-digits 2', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
  ('partial repair guard 1', (29809251, '2026-05-01', '2026-07-15', 1000, 2, 3), [298093, 'converted']),
  ('partial repair guard 2', (-2819546, '2026-05-21', '2026-07-20', 1000, 2, 3), [-28195, 'converted']),
  ('control: after', (77, '2027-01-01', '2026-07-01', 10000, 2, 2), [77, 'native']),
  ('control: small old amount', (4999, '2026-06-01', '2026-07-01', 10000, 2, 2), [0, 'converted']),
  ('control: before cutover', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
  ('control: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])]]
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 old-minor-digits 1[50, 'converted'][50, 'converted']Passed
regression old-minor-digits 2[123457, 'converted'][123457, 'converted']Passed
control: before cutover[123, 'converted'][123, 'converted']Passed
control: negative tie[-2, 'converted'][-2, 'converted']Passed
control: cutover day[500, 'native'][500, 'native']Passed
control: exponent gain[123457, 'converted'][123457, 'converted']Passed

SHA-256 / 1f06a54a69cea767d6f0e57e693e41bdeb1ec6855a0d3650b4105a7e2254435d

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

Case digest / eb2ed516870617a8c9a5eeaa608c9ce2fc107a1178cf82de2092bf71777fc8e9