FA-62116 / Currency rounding and FX conversion / Open access
The sign of a negative amount is applied only to the whole-unit part · case 01
'-12.50' parses as -1150 cents and '-0.50' as +50.
ROOT CAUSE
The minus sign multiplies only the integer part before the fraction is added.
VERIFIED REPAIR
Parse the magnitude, then negate the whole minor-unit value.
Unsuccessful approach: Deriving the sign from the parsed whole part loses it when the whole part is zero.
Case contract
solve(text, locale, exp): parse a localized amount into integer minor units at exponent exp. Group/decimal separators: en ',' '.', de '.' ',', ch "'" '.', fr ' ' or U+202F and ','. An optional leading '-' marks a negative amount (including -0.xx). If grouping is used, the first group has 1-3 digits and every later group exactly 3. A decimal separator must be followed by digits. Fraction digits beyond exp are allowed only if they are zeros, otherwise 'ERR:precision'. Malformed text returns 'ERR:format'; an unknown locale 'ERR:locale'.
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
N = 1
observations = []
def solve(text, locale, exp):
seps = {'en': (',', '.'), 'de': ('.', ','), 'ch': ("'", '.'), 'fr': (' \u202f', ',')}
if locale not in seps: return 'ERR:locale'
groups, dec = seps[locale]
dig = lambda t: t != '' and all(c in '0123456789' for c in t)
s = text.strip()
neg = s.startswith('-')
if neg: s = s[1:]
whole, _, frac = s.partition(dec)
for g in groups[1:]: whole = whole.replace(g, groups[0])
parts = whole.split(groups[0])
if not all(dig(p) for p in parts) or (dec in s and not dig(frac)): return 'ERR:format'
if len(parts) > 1 and (len(parts[0]) > 3 or any(len(p) != 3 for p in parts[1:])): return 'ERR:format'
if frac[exp:].strip('0'): return 'ERR:precision'
frac = frac[:exp].ljust(exp, '0')
v = (-1 if neg else 1) * int(''.join(parts)) * 10 ** exp + (int(frac) if frac else 0)
return v
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression negative-below-one 1', ('-0,50', 'de', 2), -50),
('regression negative-below-one 2', ('-12.50', 'en', 2), -1250), ('partial repair guard 2', ('-0,500', 'fr', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')],
[('regression negative-below-one 1', ('-12.05', 'en', 2), -1205),
('regression negative-below-one 2', ('-0,500', 'fr', 2), -50), ('partial repair guard 1', ('-0.1', 'ch', 3), -100),
('partial repair guard 2', ('-0,5', 'de', 2), -50),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision'),
('control: bad grouping', ('1,23,4.00', 'en', 2), 'ERR:format'),
('control: dinar fraction', ('12.5', 'en', 3), 12500)],
[('regression negative-below-one 1', ('-12345,500', 'de', 3), -12345500),
('regression negative-below-one 2', ('-12.345,1', 'de', 2), -1234510),
('partial repair guard 1', ('-0.1', 'en', 2), -10), ('partial repair guard 2', ('-0.1', 'en', 3), -100),
('control: dinar fraction', ('12.5', 'en', 3), 12500), ('control: swiss integer', ("1'000", 'ch', 0), 1000),
('control: french plain space', ('12 345,6', 'fr', 2), 1234560),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format')],
[('regression negative-below-one 1', ('-999,50', 'fr', 3), -999500),
('regression negative-below-one 2', ('-12\u202f345,05', 'fr', 2), -1234505),
('partial repair guard 1', ('-0,500', 'de', 3), -500), ('partial repair guard 2', ('-0.1', 'ch', 2), -10),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format'),
('control: unknown locale', ('12', 'it', 2), 'ERR:locale'),
('control: oversized first group', ('1234,567.00', 'en', 2), 'ERR:format'),
('control: english grouping', ('1,234.56', 'en', 2), 123456)],
[('regression negative-below-one 1', ('-0.1', 'ch', 3), -100),
('regression negative-below-one 2', ('-1000.1', 'ch', 2), -100010),
('partial repair guard 1', ('-0,05', 'de', 3), -50), ('partial repair guard 2', ('-0,50', 'de', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')]]
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 negative-below-one 1 | 50 | -50 | Failed |
| regression negative-below-one 2 | -1150 | -1250 | Failed |
| partial repair guard 2 | 50 | -50 | Failed |
| control: english grouping | 123456 | 123456 | Passed |
| control: german short fraction | 123450 | 123450 | Passed |
| control: narrow space with trailing zero | 123450 | 123450 | Passed |
| control: excess precision | ERR:precision | ERR:precision | Passed |
SHA-256 / 4dd476b55092ea1a7cd1556cd644655c6e237332cd49ee2ff184915650ae0634
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(text, locale, exp):
seps = {'en': (',', '.'), 'de': ('.', ','), 'ch': ("'", '.'), 'fr': (' \u202f', ',')}
if locale not in seps: return 'ERR:locale'
groups, dec = seps[locale]
dig = lambda t: t != '' and all(c in '0123456789' for c in t)
s = text.strip()
neg = s.startswith('-')
if neg: s = s[1:]
whole, _, frac = s.partition(dec)
for g in groups[1:]: whole = whole.replace(g, groups[0])
parts = whole.split(groups[0])
if not all(dig(p) for p in parts) or (dec in s and not dig(frac)): return 'ERR:format'
if len(parts) > 1 and (len(parts[0]) > 3 or any(len(p) != 3 for p in parts[1:])): return 'ERR:format'
if frac[exp:].strip('0'): return 'ERR:precision'
frac = frac[:exp].ljust(exp, '0')
m = int(('-' if neg else '') + ''.join(parts))
v = m * 10 ** exp + ((-1 if m < 0 else 1) * int(frac) if frac else 0)
return v
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression negative-below-one 1', ('-0,50', 'de', 2), -50),
('regression negative-below-one 2', ('-12.50', 'en', 2), -1250), ('partial repair guard 2', ('-0,500', 'fr', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')],
[('regression negative-below-one 1', ('-12.05', 'en', 2), -1205),
('regression negative-below-one 2', ('-0,500', 'fr', 2), -50), ('partial repair guard 1', ('-0.1', 'ch', 3), -100),
('partial repair guard 2', ('-0,5', 'de', 2), -50),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision'),
('control: bad grouping', ('1,23,4.00', 'en', 2), 'ERR:format'),
('control: dinar fraction', ('12.5', 'en', 3), 12500)],
[('regression negative-below-one 1', ('-12345,500', 'de', 3), -12345500),
('regression negative-below-one 2', ('-12.345,1', 'de', 2), -1234510),
('partial repair guard 1', ('-0.1', 'en', 2), -10), ('partial repair guard 2', ('-0.1', 'en', 3), -100),
('control: dinar fraction', ('12.5', 'en', 3), 12500), ('control: swiss integer', ("1'000", 'ch', 0), 1000),
('control: french plain space', ('12 345,6', 'fr', 2), 1234560),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format')],
[('regression negative-below-one 1', ('-999,50', 'fr', 3), -999500),
('regression negative-below-one 2', ('-12\u202f345,05', 'fr', 2), -1234505),
('partial repair guard 1', ('-0,500', 'de', 3), -500), ('partial repair guard 2', ('-0.1', 'ch', 2), -10),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format'),
('control: unknown locale', ('12', 'it', 2), 'ERR:locale'),
('control: oversized first group', ('1234,567.00', 'en', 2), 'ERR:format'),
('control: english grouping', ('1,234.56', 'en', 2), 123456)],
[('regression negative-below-one 1', ('-0.1', 'ch', 3), -100),
('regression negative-below-one 2', ('-1000.1', 'ch', 2), -100010),
('partial repair guard 1', ('-0,05', 'de', 3), -50), ('partial repair guard 2', ('-0,50', 'de', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')]]
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 negative-below-one 1 | 50 | -50 | Failed |
| regression negative-below-one 2 | -1250 | -1250 | Passed |
| partial repair guard 2 | 50 | -50 | Failed |
| control: english grouping | 123456 | 123456 | Passed |
| control: german short fraction | 123450 | 123450 | Passed |
| control: narrow space with trailing zero | 123450 | 123450 | Passed |
| control: excess precision | ERR:precision | ERR:precision | Passed |
SHA-256 / a96896392e7ef0a100695292b8ff471c62972661794ce6202437f104a5bd47fe
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(text, locale, exp):
seps = {'en': (',', '.'), 'de': ('.', ','), 'ch': ("'", '.'), 'fr': (' \u202f', ',')}
if locale not in seps: return 'ERR:locale'
groups, dec = seps[locale]
dig = lambda t: t != '' and all(c in '0123456789' for c in t)
s = text.strip()
neg = s.startswith('-')
if neg: s = s[1:]
whole, _, frac = s.partition(dec)
for g in groups[1:]: whole = whole.replace(g, groups[0])
parts = whole.split(groups[0])
if not all(dig(p) for p in parts) or (dec in s and not dig(frac)): return 'ERR:format'
if len(parts) > 1 and (len(parts[0]) > 3 or any(len(p) != 3 for p in parts[1:])): return 'ERR:format'
if frac[exp:].strip('0'): return 'ERR:precision'
frac = frac[:exp].ljust(exp, '0')
v = int(''.join(parts)) * 10 ** exp + (int(frac) if frac else 0)
return -v if neg else v
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression negative-below-one 1', ('-0,50', 'de', 2), -50),
('regression negative-below-one 2', ('-12.50', 'en', 2), -1250), ('partial repair guard 2', ('-0,500', 'fr', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')],
[('regression negative-below-one 1', ('-12.05', 'en', 2), -1205),
('regression negative-below-one 2', ('-0,500', 'fr', 2), -50), ('partial repair guard 1', ('-0.1', 'ch', 3), -100),
('partial repair guard 2', ('-0,5', 'de', 2), -50),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision'),
('control: bad grouping', ('1,23,4.00', 'en', 2), 'ERR:format'),
('control: dinar fraction', ('12.5', 'en', 3), 12500)],
[('regression negative-below-one 1', ('-12345,500', 'de', 3), -12345500),
('regression negative-below-one 2', ('-12.345,1', 'de', 2), -1234510),
('partial repair guard 1', ('-0.1', 'en', 2), -10), ('partial repair guard 2', ('-0.1', 'en', 3), -100),
('control: dinar fraction', ('12.5', 'en', 3), 12500), ('control: swiss integer', ("1'000", 'ch', 0), 1000),
('control: french plain space', ('12 345,6', 'fr', 2), 1234560),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format')],
[('regression negative-below-one 1', ('-999,50', 'fr', 3), -999500),
('regression negative-below-one 2', ('-12\u202f345,05', 'fr', 2), -1234505),
('partial repair guard 1', ('-0,500', 'de', 3), -500), ('partial repair guard 2', ('-0.1', 'ch', 2), -10),
('control: dangling separator', ('12.', 'en', 2), 'ERR:format'),
('control: unknown locale', ('12', 'it', 2), 'ERR:locale'),
('control: oversized first group', ('1234,567.00', 'en', 2), 'ERR:format'),
('control: english grouping', ('1,234.56', 'en', 2), 123456)],
[('regression negative-below-one 1', ('-0.1', 'ch', 3), -100),
('regression negative-below-one 2', ('-1000.1', 'ch', 2), -100010),
('partial repair guard 1', ('-0,05', 'de', 3), -50), ('partial repair guard 2', ('-0,50', 'de', 2), -50),
('control: english grouping', ('1,234.56', 'en', 2), 123456),
('control: german short fraction', ('1.234,5', 'de', 2), 123450),
('control: narrow space with trailing zero', ('1\u202f234,500', 'fr', 2), 123450),
('control: excess precision', ('12.345', 'en', 2), 'ERR:precision')]]
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 negative-below-one 1 | -50 | -50 | Passed |
| regression negative-below-one 2 | -1250 | -1250 | Passed |
| partial repair guard 2 | -50 | -50 | Passed |
| control: english grouping | 123456 | 123456 | Passed |
| control: german short fraction | 123450 | 123450 | Passed |
| control: narrow space with trailing zero | 123450 | 123450 | Passed |
| control: excess precision | ERR:precision | ERR:precision | Passed |
SHA-256 / 3856b5a49621d7eac78512b341740e1f4519bc971ad52335ee671b2008fa06db
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:01.682038+00:00.
Case digest / 6fa3169f328c020d7ada860f0713f617bce71ac0b4edffc54f8f653c22a635c1