FAILURE MAP
← Case archive

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

The minimum fee acts as a maximum · case 01

Large transfers are charged at most the minimum fee.

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

ROOT CAUSE

fee = min(min_fee, pct) caps the percentage fee instead of flooring it.

VERIFIED REPAIR

fee = max(min_fee, pct).

Unsuccessful approach: Using the minimum only when the percentage fee is zero under-charges small non-zero fees.

Case contract

solve(amount, rate, src_exp, dst_exp, fee_bps, min_fee): amount and min_fee are source minor units, fee_bps a non-negative integer. fee = max(min_fee, ceil(amount*fee_bps/10000)) in source minor units, deducted before conversion. A fee greater than or equal to the amount returns 'ERR:amount-below-fee'; a non-positive amount or negative bps returns 'ERR:input'. The net amount is converted exactly (net * rate * 10**(dst_exp - src_exp)) and rounded half-even. Return [fee, converted].

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, rate, src_exp, dst_exp, fee_bps, min_fee):
    if fee_bps < 0 or amount <= 0: return 'ERR:input'
    conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
    rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
    pct = -(-amount * fee_bps // 10000)
    fee = min(min_fee, pct)
    if fee >= amount: return 'ERR:amount-below-fee'
    return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression minimum-fee 1', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('regression minimum-fee 2', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('partial repair guard 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('regression minimum-fee 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 2', (314862, '122.2171', 2, 2, 5, 1000), [1000, 38359303]),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('regression minimum-fee 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (6382, '2724.83', 3, 0, 333, 1000), [1000, 14665]),
  ('partial repair guard 2', (209552, '10.19418', 2, 3, 5, 250), [250, 21336623]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee')],
 [('regression minimum-fee 1', (197637, '16116.34', 0, 2, 333, 100), [6582, 307910733870]),
  ('regression minimum-fee 2', (22226, '179.9447', 0, 2, 150, 100), [334, 393934937]),
  ('partial repair guard 1', (196707, '199.6780', 0, 3, 25, 1000), [1000, 39078382346]),
  ('partial repair guard 2', (8653, '7.1259', 3, 3, 99, 250), [250, 59879]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
 [('regression minimum-fee 1', (50411, '12342.04', 3, 3, 333, 1), [1679, 601452293]),
  ('regression minimum-fee 2', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('partial repair guard 1', (280407, '11.52605', 3, 0, 25, 1000), [1000, 3220]),
  ('partial repair guard 2', (453829, '90.1289', 3, 3, 5, 250), [250, 40880576]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: minimum exceeds amount', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee')]]
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 minimum-fee 1[50, 61266][100, 61113]Failed
regression minimum-fee 2[50, 15061][150, 14910]Failed
partial repair guard 2[1, 449]ERR:amount-below-feeFailed
control: fee rounds up[0, 1001][11, 990]Failed
control: zero bps[0, 842][0, 842]Passed
control: large percentage fee[199, 113396][3087, 110739]Failed
control: invalid amountERR:inputERR:inputPassed

SHA-256 / 9a42fee69f5250a82eea246b1e0f18e215a4ae5d56bfa71456f282fa54b4b651

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, rate, src_exp, dst_exp, fee_bps, min_fee):
    if fee_bps < 0 or amount <= 0: return 'ERR:input'
    conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
    rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
    pct = -(-amount * fee_bps // 10000)
    fee = pct or min_fee
    if fee >= amount: return 'ERR:amount-below-fee'
    return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression minimum-fee 1', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('regression minimum-fee 2', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('partial repair guard 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('regression minimum-fee 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 2', (314862, '122.2171', 2, 2, 5, 1000), [1000, 38359303]),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('regression minimum-fee 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (6382, '2724.83', 3, 0, 333, 1000), [1000, 14665]),
  ('partial repair guard 2', (209552, '10.19418', 2, 3, 5, 250), [250, 21336623]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee')],
 [('regression minimum-fee 1', (197637, '16116.34', 0, 2, 333, 100), [6582, 307910733870]),
  ('regression minimum-fee 2', (22226, '179.9447', 0, 2, 150, 100), [334, 393934937]),
  ('partial repair guard 1', (196707, '199.6780', 0, 3, 25, 1000), [1000, 39078382346]),
  ('partial repair guard 2', (8653, '7.1259', 3, 3, 99, 250), [250, 59879]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
 [('regression minimum-fee 1', (50411, '12342.04', 3, 3, 333, 1), [1679, 601452293]),
  ('regression minimum-fee 2', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('partial repair guard 1', (280407, '11.52605', 3, 0, 25, 1000), [1000, 3220]),
  ('partial repair guard 2', (453829, '90.1289', 3, 3, 5, 250), [250, 40880576]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: minimum exceeds amount', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee')]]
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 minimum-fee 1[50, 61266][100, 61113]Failed
regression minimum-fee 2[150, 14910][150, 14910]Passed
partial repair guard 2[1, 449]ERR:amount-below-feeFailed
control: fee rounds up[11, 990][11, 990]Passed
control: zero bps[0, 842][0, 842]Passed
control: large percentage fee[3087, 110739][3087, 110739]Passed
control: invalid amountERR:inputERR:inputPassed

SHA-256 / 6718119407eb40f0f15743031cad768adc2f1d6f8abc1e0ebfaf676528271d2d

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, rate, src_exp, dst_exp, fee_bps, min_fee):
    if fee_bps < 0 or amount <= 0: return 'ERR:input'
    conv = lambda m: Decimal(m) * Decimal(rate) * Decimal(10) ** (dst_exp - src_exp)
    rq = lambda d: int(d.quantize(Decimal(1), rounding=ROUND_HALF_EVEN))
    pct = -(-amount * fee_bps // 10000)
    fee = max(min_fee, pct)
    if fee >= amount: return 'ERR:amount-below-fee'
    return [fee, rq(conv(amount - fee))]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
cases = [[('regression minimum-fee 1', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('regression minimum-fee 2', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('partial repair guard 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('regression minimum-fee 2', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 2', (314862, '122.2171', 2, 2, 5, 1000), [1000, 38359303]),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input')],
 [('regression minimum-fee 1', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('regression minimum-fee 2', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee'),
  ('partial repair guard 1', (6382, '2724.83', 3, 0, 333, 1000), [1000, 14665]),
  ('partial repair guard 2', (209552, '10.19418', 2, 3, 5, 250), [250, 21336623]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee')],
 [('regression minimum-fee 1', (197637, '16116.34', 0, 2, 333, 100), [6582, 307910733870]),
  ('regression minimum-fee 2', (22226, '179.9447', 0, 2, 150, 100), [334, 393934937]),
  ('partial repair guard 1', (196707, '199.6780', 0, 3, 25, 1000), [1000, 39078382346]),
  ('partial repair guard 2', (8653, '7.1259', 3, 3, 99, 250), [250, 59879]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('control: fee equals amount', (500, '0.9', 2, 2, 10, 500), 'ERR:amount-below-fee'),
  ('control: minimum fee applies', (20000, '0.3071', 2, 3, 25, 100), [100, 61113]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842])],
 [('regression minimum-fee 1', (50411, '12342.04', 3, 3, 333, 1), [1679, 601452293]),
  ('regression minimum-fee 2', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('partial repair guard 1', (280407, '11.52605', 3, 0, 25, 1000), [1000, 3220]),
  ('partial repair guard 2', (453829, '90.1289', 3, 3, 5, 250), [250, 40880576]),
  ('control: zero bps', (777, '1.0833', 2, 2, 0, 0), [0, 842]),
  ('control: large percentage fee', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('control: invalid amount', (0, '1.1', 2, 2, 10, 0), 'ERR:input'),
  ('control: minimum exceeds amount', (99, '1.2', 2, 2, 5, 150), 'ERR:amount-below-fee')]]
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 minimum-fee 1[100, 61113][100, 61113]Passed
regression minimum-fee 2[150, 14910][150, 14910]Passed
partial repair guard 2ERR:amount-below-feeERR:amount-below-feePassed
control: fee rounds up[11, 990][11, 990]Passed
control: zero bps[0, 842][0, 842]Passed
control: large percentage fee[3087, 110739][3087, 110739]Passed
control: invalid amountERR:inputERR:inputPassed

SHA-256 / c44e685af1c98986585cc802df86fc566c664c3ff4e14198dcc3e314485e2704

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

Case digest / 1174f62ae9cd7455c87c5a84a9d1059afedb3c37d50a18dbf7e95eed2d491152