FA-62246 / Currency rounding and FX conversion / Open access
Redenominated amounts are truncated · case 01
Old balances lose up to one new minor unit, and debts shrink toward zero.
ROOT CAUSE
int(v) truncates instead of rounding half away from zero.
VERIFIED REPAIR
Round half away from zero (ROUND_HALF_UP) before converting to int.
Unsuccessful approach: Rounding toward positive infinity over-credits positive balances and under-states debts.
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), 'converted']
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression redenomination-rounding 1', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
('regression redenomination-rounding 2', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
('partial repair guard 2', (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: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
('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 redenomination-rounding 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
('regression redenomination-rounding 2', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
('partial repair guard 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
('partial repair guard 2', (13591002, '2026-07-05', '2026-07-15', 1000, 2, 3), [135910, '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: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])],
[('regression redenomination-rounding 1', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
('regression redenomination-rounding 2', (13003559, '2026-07-14', '2026-07-20', 10000, 2, 3), [13004, 'converted']),
('partial repair guard 1', (10748129, '2026-07-06', '2026-07-20', 10000, 0, 2), [107481, 'converted']),
('partial repair guard 2', (8329740, '2026-05-01', '2026-07-20', 100, 2, 2), [83297, '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 redenomination-rounding 1', (8481473, '2026-05-16', '2026-07-15', 10000, 0, 2), [84815, 'converted']),
('regression redenomination-rounding 2', (21804659, '2026-05-04', '2026-07-15', 100, 0, 0), [218047, 'converted']),
('partial repair guard 1', (3215020, '2026-06-17', '2026-07-15', 1000, 0, 0), [3215, 'converted']),
('partial repair guard 2', (21342190, '2026-05-23', '2026-07-15', 10000, 0, 0), [2134, '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 redenomination-rounding 1', (20559948, '2026-07-01', '2026-07-20', 10000, 0, 3), [2055995, 'converted']),
('regression redenomination-rounding 2', (4665955, '2026-06-23', '2026-07-20', 10000, 0, 0), [467, 'converted']),
('partial repair guard 1', (1650321, '2026-06-28', '2026-07-15', 10000, 0, 3), [165032, 'converted']),
('partial repair guard 2', (-1534046, '2026-05-28', '2026-07-20', 10000, 0, 3), [-153405, '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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression redenomination-rounding 1 | [-1, 'converted'] | [-2, 'converted'] | Failed |
| regression redenomination-rounding 2 | [123456, 'converted'] | [123457, 'converted'] | Failed |
| partial repair guard 2 | [0, 'converted'] | [0, 'converted'] | Passed |
| control: before cutover | [123, 'converted'] | [123, 'converted'] | Passed |
| control: cutover day | [500, 'native'] | [500, 'native'] | Passed |
| control: same month earlier | [999, 'converted'] | [1000, 'converted'] | Failed |
| control: after | [77, 'native'] | [77, 'native'] | Passed |
SHA-256 / 9ab22fc24ffce2c800c154e0b8f9c64239d5536fc3653450fa0a1d9cc2d1bb6b
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) / factor * Decimal(10) ** (new_exp - old_exp)
return [int(v.quantize(Decimal(1), rounding=ROUND_CEILING)), 'converted']
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression redenomination-rounding 1', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
('regression redenomination-rounding 2', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
('partial repair guard 2', (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: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
('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 redenomination-rounding 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
('regression redenomination-rounding 2', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
('partial repair guard 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
('partial repair guard 2', (13591002, '2026-07-05', '2026-07-15', 1000, 2, 3), [135910, '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: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])],
[('regression redenomination-rounding 1', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
('regression redenomination-rounding 2', (13003559, '2026-07-14', '2026-07-20', 10000, 2, 3), [13004, 'converted']),
('partial repair guard 1', (10748129, '2026-07-06', '2026-07-20', 10000, 0, 2), [107481, 'converted']),
('partial repair guard 2', (8329740, '2026-05-01', '2026-07-20', 100, 2, 2), [83297, '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 redenomination-rounding 1', (8481473, '2026-05-16', '2026-07-15', 10000, 0, 2), [84815, 'converted']),
('regression redenomination-rounding 2', (21804659, '2026-05-04', '2026-07-15', 100, 0, 0), [218047, 'converted']),
('partial repair guard 1', (3215020, '2026-06-17', '2026-07-15', 1000, 0, 0), [3215, 'converted']),
('partial repair guard 2', (21342190, '2026-05-23', '2026-07-15', 10000, 0, 0), [2134, '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 redenomination-rounding 1', (20559948, '2026-07-01', '2026-07-20', 10000, 0, 3), [2055995, 'converted']),
('regression redenomination-rounding 2', (4665955, '2026-06-23', '2026-07-20', 10000, 0, 0), [467, 'converted']),
('partial repair guard 1', (1650321, '2026-06-28', '2026-07-15', 10000, 0, 3), [165032, 'converted']),
('partial repair guard 2', (-1534046, '2026-05-28', '2026-07-20', 10000, 0, 3), [-153405, '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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression redenomination-rounding 1 | [-1, 'converted'] | [-2, 'converted'] | Failed |
| regression redenomination-rounding 2 | [123457, 'converted'] | [123457, 'converted'] | Passed |
| partial repair guard 2 | [1, 'converted'] | [0, 'converted'] | Failed |
| control: before cutover | [124, 'converted'] | [123, 'converted'] | Failed |
| control: cutover day | [500, 'native'] | [500, 'native'] | Passed |
| control: same month earlier | [1000, 'converted'] | [1000, 'converted'] | Passed |
| control: after | [77, 'native'] | [77, 'native'] | Passed |
SHA-256 / cc548e1e0cd3006df319f5507c3ad8f83ba82bcf545d621f01b0475be48f711d
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 redenomination-rounding 1', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted']),
('regression redenomination-rounding 2', (1234567, '2025-12-31', '2026-01-01', 1000, 0, 2), [123457, 'converted']),
('partial repair guard 2', (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: cutover day', (500, '2026-07-01', '2026-07-01', 10000, 2, 2), [500, 'native']),
('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 redenomination-rounding 1', (99999, '2026-07-15', '2026-07-20', 100, 2, 2), [1000, 'converted']),
('regression redenomination-rounding 2', (8690380, '2026-05-13', '2026-07-01', 100, 2, 2), [86904, 'converted']),
('partial repair guard 1', (1234567, '2026-06-30', '2026-07-01', 10000, 2, 2), [123, 'converted']),
('partial repair guard 2', (13591002, '2026-07-05', '2026-07-15', 1000, 2, 3), [135910, '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: negative tie', (-15000, '2026-01-01', '2026-07-01', 10000, 2, 2), [-2, 'converted'])],
[('regression redenomination-rounding 1', (15980147, '2026-05-22', '2026-07-15', 10000, 0, 3), [1598015, 'converted']),
('regression redenomination-rounding 2', (13003559, '2026-07-14', '2026-07-20', 10000, 2, 3), [13004, 'converted']),
('partial repair guard 1', (10748129, '2026-07-06', '2026-07-20', 10000, 0, 2), [107481, 'converted']),
('partial repair guard 2', (8329740, '2026-05-01', '2026-07-20', 100, 2, 2), [83297, '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 redenomination-rounding 1', (8481473, '2026-05-16', '2026-07-15', 10000, 0, 2), [84815, 'converted']),
('regression redenomination-rounding 2', (21804659, '2026-05-04', '2026-07-15', 100, 0, 0), [218047, 'converted']),
('partial repair guard 1', (3215020, '2026-06-17', '2026-07-15', 1000, 0, 0), [3215, 'converted']),
('partial repair guard 2', (21342190, '2026-05-23', '2026-07-15', 10000, 0, 0), [2134, '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 redenomination-rounding 1', (20559948, '2026-07-01', '2026-07-20', 10000, 0, 3), [2055995, 'converted']),
('regression redenomination-rounding 2', (4665955, '2026-06-23', '2026-07-20', 10000, 0, 0), [467, 'converted']),
('partial repair guard 1', (1650321, '2026-06-28', '2026-07-15', 10000, 0, 3), [165032, 'converted']),
('partial repair guard 2', (-1534046, '2026-05-28', '2026-07-20', 10000, 0, 3), [-153405, '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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| regression redenomination-rounding 1 | [-2, 'converted'] | [-2, 'converted'] | Passed |
| regression redenomination-rounding 2 | [123457, 'converted'] | [123457, 'converted'] | Passed |
| partial repair guard 2 | [0, 'converted'] | [0, 'converted'] | Passed |
| control: before cutover | [123, 'converted'] | [123, 'converted'] | Passed |
| control: cutover day | [500, 'native'] | [500, 'native'] | Passed |
| control: same month earlier | [1000, 'converted'] | [1000, 'converted'] | Passed |
| control: after | [77, 'native'] | [77, 'native'] | Passed |
SHA-256 / 2b5cbee864512107121750c2851f7423b3f815fff258cabc4b8b424a868cfa75
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.774382+00:00.
Case digest / d1a7e69c37890332ada5c2dc879946603af6b0a6c42065f027039a80e2c44961