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.
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| 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