FAILURE MAP
← Case archive

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

The percentage fee is floored instead of rounded up · case 01

A 1% fee on 1001 minor units is 10 instead of 11.

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

ROOT CAUSE

Integer division amount*bps//10000 truncates the fee.

THE FAILURE

Integer division amount*bps//10000 truncates the fee.

Unsuccessful approach: Rounding the fee to nearest with float division still under-charges fractions below one half.

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 = 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 fee-ceiling 1', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('regression fee-ceiling 2', (1003, '1', 2, 2, 100, 0), [11, 992]),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('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 fee-ceiling 1', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('regression fee-ceiling 2', (197637, '16116.34', 0, 2, 333, 100), [6582, 307910733870]),
  ('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: 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')],
 [('regression fee-ceiling 1', (22226, '179.9447', 0, 2, 150, 100), [334, 393934937]),
  ('regression fee-ceiling 2', (50411, '12342.04', 3, 3, 333, 1), [1679, 601452293]),
  ('partial repair guard 2', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('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'),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990])],
 [('regression fee-ceiling 1', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('regression fee-ceiling 2', (269350, '9.84392', 0, 3, 333, 0), [8970, 2563159890]),
  ('partial repair guard 2', (84375, '129.7381', 0, 0, 99, 250), [836, 10838191]),
  ('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 fee-ceiling 1', (84375, '129.7381', 0, 0, 99, 250), [836, 10838191]),
  ('regression fee-ceiling 2', (90216, '1.62136', 0, 2, 1000, 250), [9022, 13164470]),
  ('partial repair guard 1', (234309, '91.3439', 3, 3, 333, 100), [7803, 20689941]),
  ('partial repair guard 2', (462525, '13331.53', 0, 0, 1000, 50), [46253, 5549542656]),
  ('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 fee-ceiling 1[10, 991][11, 990]Failed
regression fee-ceiling 2[10, 993][11, 992]Failed
control: yen payout[150, 14910][150, 14910]Passed
control: fee equals amountERR:amount-below-feeERR:amount-below-feePassed
control: minimum fee applies[100, 61113][100, 61113]Passed
control: zero bps[0, 842][0, 842]Passed

SHA-256 / 43330627406d1d069de6528f0ac2a219675b89b9f4438101d255df3404ee2804

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 = round(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 fee-ceiling 1', (1001, '1', 2, 2, 100, 0), [11, 990]),
  ('regression fee-ceiling 2', (1003, '1', 2, 2, 100, 0), [11, 992]),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('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 fee-ceiling 1', (123456, '0.92', 2, 2, 250, 199), [3087, 110739]),
  ('regression fee-ceiling 2', (197637, '16116.34', 0, 2, 333, 100), [6582, 307910733870]),
  ('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: 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')],
 [('regression fee-ceiling 1', (22226, '179.9447', 0, 2, 150, 100), [334, 393934937]),
  ('regression fee-ceiling 2', (50411, '12342.04', 3, 3, 333, 1), [1679, 601452293]),
  ('partial repair guard 2', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('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'),
  ('control: yen payout', (10000, '151.37', 2, 0, 150, 50), [150, 14910]),
  ('control: fee rounds up', (1001, '1', 2, 2, 100, 0), [11, 990])],
 [('regression fee-ceiling 1', (268603, '16.66106', 2, 0, 150, 50), [4030, 44081]),
  ('regression fee-ceiling 2', (269350, '9.84392', 0, 3, 333, 0), [8970, 2563159890]),
  ('partial repair guard 2', (84375, '129.7381', 0, 0, 99, 250), [836, 10838191]),
  ('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 fee-ceiling 1', (84375, '129.7381', 0, 0, 99, 250), [836, 10838191]),
  ('regression fee-ceiling 2', (90216, '1.62136', 0, 2, 1000, 250), [9022, 13164470]),
  ('partial repair guard 1', (234309, '91.3439', 3, 3, 333, 100), [7803, 20689941]),
  ('partial repair guard 2', (462525, '13331.53', 0, 0, 1000, 50), [46253, 5549542656]),
  ('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 fee-ceiling 1[10, 991][11, 990]Failed
regression fee-ceiling 2[10, 993][11, 992]Failed
control: yen payout[150, 14910][150, 14910]Passed
control: fee equals amountERR:amount-below-feeERR:amount-below-feePassed
control: minimum fee applies[100, 61113][100, 61113]Passed
control: zero bps[0, 842][0, 842]Passed

SHA-256 / d1adb7c550a70856b47e8f24a1fd018080481eea9cbed10214375553b4acea3b

HELD IN THE MEMBER ARCHIVE

The verified repair and its recorded checks are member-only.

This mechanism has 6 recorded checks per implementation. The open-access tier publishes the failure and the unsuccessful fix; the repaired source that passes every check, and the observations that prove it, are available to members.

Every case sharing this mechanism uses the same contract and the same repair, so this one record is held back for all of them.

Member access is invitation-based. Sign in with your invited account to inspect the repair.

Sign in to the archive ↗

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

Case digest / 3ce81392c642850b9fc5d1bda7a7897a2c7bcf9fe74fc0824d72476d3497ddcb