FAILURE MAP
← Case archive

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

Each micro-payment is floored independently without carrying the remainder · case 01

Three postings of 0.4 units each convert to zero in total instead of one unit.

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

ROOT CAUSE

Each posting is converted and floored on its own, discarding the fractional carry.

VERIFIED REPAIR

Floor the running daily total and post the difference from the previous floor.

Unsuccessful approach: Rounding each posting half-even on its own still loses or invents units across a day.

Case contract

solve(items, rate): items are [day, amount] with integer source minor units (reversals negative) in posting order; rate is target minor units per source minor unit. Within a day, each posting's converted amount is floor(cumulative_exact) - floor(previous cumulative_exact), so daily converted postings sum to floor of the day's exact converted total. The carry (cumulative total and previous floor) resets whenever the day changes. Return the list of integer converted postings.

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(items, rate):
    out, cum, prev_floor, day = [], Decimal(0), 0, None
    r = Decimal(rate)
    for d, a in items:
        if d != day:
            day, cum, prev_floor = d, Decimal(0), 0
        out.append(int((Decimal(a) * r).to_integral_value(rounding=ROUND_FLOOR)))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression carry-accumulation 1', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 1], ['d1', 1]], '0.6'), [0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1])],
 [('regression carry-accumulation 1', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('partial repair guard 1', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', 186], ['2026-03-01', -5], ['2026-03-02', 8], ['2026-03-02', 154]], '0.3542'), [65, -1, 2, 55]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('partial repair guard 1',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), [])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', -18], ['2026-03-01', 240], ['2026-03-02', -19], ['2026-03-02', 81], ['2026-03-02', 129],
     ['2026-03-02', 231]],
    '0.2472'),
   [-5, 59, -5, 20, 32, 57]),
  ('regression carry-accumulation 2',
   ([['2026-03-02', 92], ['2026-03-03', 36], ['2026-03-03', -15], ['2026-04-01', 46], ['2026-04-01', 50],
     ['2026-04-01', 95]],
    '4.346'),
   [399, 156, -65, 199, 218, 413]),
  ('partial repair guard 1',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 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 carry-accumulation 1[0, 0, 0][0, 0, 1]Failed
regression carry-accumulation 2[0, 0][0, 1]Failed
control: day reset[0, 0, 0][0, 0, 1]Failed
control: reversal[2, -2][2, -1]Failed
control: single large[11375][11375]Passed
control: month change[1, 1][1, 1]Passed

SHA-256 / 1d34a8d2274de85e6928a9f8c9ce242270ca621747bc602ec8b036b73174184f

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(items, rate):
    out, cum, prev_floor, day = [], Decimal(0), 0, None
    r = Decimal(rate)
    for d, a in items:
        if d != day:
            day, cum, prev_floor = d, Decimal(0), 0
        out.append(int((Decimal(a) * r).to_integral_value(rounding=ROUND_HALF_EVEN)))
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression carry-accumulation 1', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 1], ['d1', 1]], '0.6'), [0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1])],
 [('regression carry-accumulation 1', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('partial repair guard 1', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', 186], ['2026-03-01', -5], ['2026-03-02', 8], ['2026-03-02', 154]], '0.3542'), [65, -1, 2, 55]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('partial repair guard 1',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), [])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', -18], ['2026-03-01', 240], ['2026-03-02', -19], ['2026-03-02', 81], ['2026-03-02', 129],
     ['2026-03-02', 231]],
    '0.2472'),
   [-5, 59, -5, 20, 32, 57]),
  ('regression carry-accumulation 2',
   ([['2026-03-02', 92], ['2026-03-03', 36], ['2026-03-03', -15], ['2026-04-01', 46], ['2026-04-01', 50],
     ['2026-04-01', 95]],
    '4.346'),
   [399, 156, -65, 199, 218, 413]),
  ('partial repair guard 1',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 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 carry-accumulation 1[0, 0, 0][0, 0, 1]Failed
regression carry-accumulation 2[1, 1][0, 1]Failed
control: day reset[1, 1, 1][0, 0, 1]Failed
control: reversal[2, -2][2, -1]Failed
control: single large[11376][11375]Failed
control: month change[1, 1][1, 1]Passed

SHA-256 / db58a63cf09ecea934f84a588545b6321cb04f38df820daef59148d719aa7f7b

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(items, rate):
    out, cum, prev_floor, day = [], Decimal(0), 0, None
    r = Decimal(rate)
    for d, a in items:
        if d != day:
            day, cum, prev_floor = d, Decimal(0), 0
        cum += Decimal(a) * r
        f = int(cum.to_integral_value(rounding=ROUND_FLOOR))
        out.append(f - prev_floor)
        prev_floor = f
    return out
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression carry-accumulation 1', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 1], ['d1', 1]], '0.6'), [0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('regression carry-accumulation 2', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1])],
 [('regression carry-accumulation 1', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('partial repair guard 1', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 1]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', 186], ['2026-03-01', -5], ['2026-03-02', 8], ['2026-03-02', 154]], '0.3542'), [65, -1, 2, 55]),
  ('regression carry-accumulation 2',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('partial repair guard 1',
   ([['2026-03-01', 286], ['2026-03-01', 294], ['2026-03-01', 293], ['2026-03-01', 194], ['2026-03-01', 25],
     ['2026-03-01', 203]],
    '4.921'),
   [1407, 1447, 1442, 954, 123, 999]),
  ('control: reversal', ([['d1', 5], ['d1', -3]], '0.5'), [2, -1]),
  ('control: single large', ([['2026-03-01', 12345]], '0.9215'), [11375]),
  ('control: month change', ([['2026-03-31', 3], ['2026-04-01', 3]], '0.35'), [1, 1]),
  ('control: empty', ([], '1.5'), [])],
 [('regression carry-accumulation 1',
   ([['2026-03-01', -18], ['2026-03-01', 240], ['2026-03-02', -19], ['2026-03-02', 81], ['2026-03-02', 129],
     ['2026-03-02', 231]],
    '0.2472'),
   [-5, 59, -5, 20, 32, 57]),
  ('regression carry-accumulation 2',
   ([['2026-03-02', 92], ['2026-03-03', 36], ['2026-03-03', -15], ['2026-04-01', 46], ['2026-04-01', 50],
     ['2026-04-01', 95]],
    '4.346'),
   [399, 156, -65, 199, 218, 413]),
  ('partial repair guard 1',
   ([['2026-03-01', 263], ['2026-03-01', 239], ['2026-03-01', 228], ['2026-03-01', -26], ['2026-03-02', 231]], '29.57'),
   [7776, 7068, 6742, -769, 6830]),
  ('control: empty', ([], '1.5'), []),
  ('control: negative first', ([['d1', -1], ['d1', -1], ['d1', 2]], '0.3'), [-1, 0, 1]),
  ('control: three micro postings', ([['d1', 1], ['d1', 1], ['d1', 1]], '0.4'), [0, 0, 1]),
  ('control: day reset', ([['d1', 1], ['d2', 1], ['d2', 1]], '0.6'), [0, 0, 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 carry-accumulation 1[0, 0, 1][0, 0, 1]Passed
regression carry-accumulation 2[0, 1][0, 1]Passed
control: day reset[0, 0, 1][0, 0, 1]Passed
control: reversal[2, -1][2, -1]Passed
control: single large[11375][11375]Passed
control: month change[1, 1][1, 1]Passed

SHA-256 / 6cc8c76f84ffceb27c79e51c329aa61b0467f3ab0a7acb9c948d5fa89cee8835

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

Case digest / 2b5c8d46c6cec16fed7921a73181d8d84dc7eaa60164294e9dbf0db3bd0038a8