FAILURE MAP
← Case archive

FA-84381 / Betting odds conversion / Open access

Decimal price rounded with binary floats or truncated · case 01

A -800 line shows 1.12 instead of 1.13.

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

ROOT CAUSE

The decimal price is formatted with '%.2f' on a float, rounding the binary value.

VERIFIED REPAIR

Round the exact value half up.

Unsuccessful approach: Truncating inside the helper also drops the half-cent.

Case contract

Expand an American moneyline ("+150", "150" or "-200"; magnitude at least 100, otherwise "invalid"). Profit per unit is N/100 for positive or unsigned lines and 100/N for negative lines. Return [decimal price with two decimals, reduced fractional profit "a/b", implied probability percent with one decimal], all computed exactly and rounded half up.

Why this case matters

Odds screens show American, decimal, fractional and implied-probability columns for one price.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(american):
    s = american.strip()
    neg = s.startswith('-')
    n = int(s.lstrip('+-'))
    if n < 100:
        return 'invalid'
    profit = Fraction(100, n) if neg else Fraction(n, 100)
    dec = 1 + profit
    def fmt(x, places):
        q = 10 ** places
        v = math.floor(x * q + Fraction(1, 2))
        return '%d.%0*d' % (v // q, places, v % q)
    prob = Fraction(n, n + 100) * 100 if neg else Fraction(100, n + 100) * 100
    return ['%.2f' % float(dec), '%d/%d' % (profit.numerator, profit.denominator), fmt(prob, 1)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-331',), ['1.30', '100/331', '76.8']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-100',), ['2.00', '1/1', '50.0']),
  ('variant scenario 2', ('+208',), ['3.08', '52/25', '32.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('895',), ['9.95', '179/20', '10.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-120',), ['1.83', '5/6', '54.5']),
  ('variant scenario 2', ('+210',), ['3.10', '21/10', '32.3'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-1004',), ['1.10', '25/251', '90.9']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+168',), ['2.68', '42/25', '37.3']),
  ('variant scenario 2', ('217',), ['3.17', '217/100', '31.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-674',), ['1.15', '50/337', '87.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+50',), 'invalid'),
  ('variant scenario 2', ('149',), ['2.49', '149/100', '40.2'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-624',), ['1.16', '25/156', '86.2']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+125',), ['2.25', '5/4', '44.4']),
  ('variant scenario 2', ('+119',), ['2.19', '119/100', '45.7'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(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
control underdog['2.50', '3/2', '40.0']['2.50', '3/2', '40.0']Passed
control favourite['1.50', '1/2', '66.7']['1.50', '1/2', '66.7']Passed
boundary even money['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
boundary unsigned line['3.50', '5/2', '28.6']['3.50', '5/2', '28.6']Passed
boundary below minimuminvalidinvalidPassed
control half-up decimal['1.12', '1/8', '88.9']['1.13', '1/8', '88.9']Failed
regression: decimal rounding['1.30', '100/331', '76.8']['1.30', '100/331', '76.8']Passed
regression: decimal rounding['1.12', '1/8', '88.9']['1.13', '1/8', '88.9']Failed
variant scenario 1['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
variant scenario 2['3.08', '52/25', '32.5']['3.08', '52/25', '32.5']Passed

SHA-256 / 7b0ca169891b24758852e34751cd0f82ef72e8b1edb915075190bbbac9932e57

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(american):
    s = american.strip()
    neg = s.startswith('-')
    n = int(s.lstrip('+-'))
    if n < 100:
        return 'invalid'
    profit = Fraction(100, n) if neg else Fraction(n, 100)
    dec = 1 + profit
    def fmt(x, places):
        q = 10 ** places
        v = math.floor(x * q)
        return '%d.%0*d' % (v // q, places, v % q)
    prob = Fraction(n, n + 100) * 100 if neg else Fraction(100, n + 100) * 100
    return [fmt(dec, 2), '%d/%d' % (profit.numerator, profit.denominator), fmt(prob, 1)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-331',), ['1.30', '100/331', '76.8']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-100',), ['2.00', '1/1', '50.0']),
  ('variant scenario 2', ('+208',), ['3.08', '52/25', '32.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('895',), ['9.95', '179/20', '10.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-120',), ['1.83', '5/6', '54.5']),
  ('variant scenario 2', ('+210',), ['3.10', '21/10', '32.3'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-1004',), ['1.10', '25/251', '90.9']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+168',), ['2.68', '42/25', '37.3']),
  ('variant scenario 2', ('217',), ['3.17', '217/100', '31.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-674',), ['1.15', '50/337', '87.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+50',), 'invalid'),
  ('variant scenario 2', ('149',), ['2.49', '149/100', '40.2'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-624',), ['1.16', '25/156', '86.2']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+125',), ['2.25', '5/4', '44.4']),
  ('variant scenario 2', ('+119',), ['2.19', '119/100', '45.7'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(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
control underdog['2.50', '3/2', '40.0']['2.50', '3/2', '40.0']Passed
control favourite['1.50', '1/2', '66.6']['1.50', '1/2', '66.7']Failed
boundary even money['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
boundary unsigned line['3.50', '5/2', '28.5']['3.50', '5/2', '28.6']Failed
boundary below minimuminvalidinvalidPassed
control half-up decimal['1.12', '1/8', '88.8']['1.13', '1/8', '88.9']Failed
regression: decimal rounding['1.30', '100/331', '76.7']['1.30', '100/331', '76.8']Failed
regression: decimal rounding['1.12', '1/8', '88.8']['1.13', '1/8', '88.9']Failed
variant scenario 1['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
variant scenario 2['3.08', '52/25', '32.4']['3.08', '52/25', '32.5']Failed

SHA-256 / cb3d673a76b3f8f0643c0f87e8fad94700425cc51c7dfb658bcc741df98a7342

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(american):
    s = american.strip()
    neg = s.startswith('-')
    n = int(s.lstrip('+-'))
    if n < 100:
        return 'invalid'
    profit = Fraction(100, n) if neg else Fraction(n, 100)
    dec = 1 + profit
    def fmt(x, places):
        q = 10 ** places
        v = math.floor(x * q + Fraction(1, 2))
        return '%d.%0*d' % (v // q, places, v % q)
    prob = Fraction(n, n + 100) * 100 if neg else Fraction(100, n + 100) * 100
    return [fmt(dec, 2), '%d/%d' % (profit.numerator, profit.denominator), fmt(prob, 1)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-331',), ['1.30', '100/331', '76.8']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-100',), ['2.00', '1/1', '50.0']),
  ('variant scenario 2', ('+208',), ['3.08', '52/25', '32.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('895',), ['9.95', '179/20', '10.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('-120',), ['1.83', '5/6', '54.5']),
  ('variant scenario 2', ('+210',), ['3.10', '21/10', '32.3'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-1004',), ['1.10', '25/251', '90.9']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+168',), ['2.68', '42/25', '37.3']),
  ('variant scenario 2', ('217',), ['3.17', '217/100', '31.5'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-674',), ['1.15', '50/337', '87.1']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+50',), 'invalid'),
  ('variant scenario 2', ('149',), ['2.49', '149/100', '40.2'])],
 [('control underdog', ('+150',), ['2.50', '3/2', '40.0']),
  ('control favourite', ('-200',), ['1.50', '1/2', '66.7']),
  ('boundary even money', ('+100',), ['2.00', '1/1', '50.0']),
  ('boundary unsigned line', ('250',), ['3.50', '5/2', '28.6']),
  ('boundary below minimum', ('+50',), 'invalid'),
  ('control half-up decimal', ('-800',), ['1.13', '1/8', '88.9']),
  ('regression: decimal rounding', ('-624',), ['1.16', '25/156', '86.2']),
  ('regression: decimal rounding', ('-800',), ['1.13', '1/8', '88.9']),
  ('variant scenario 1', ('+125',), ['2.25', '5/4', '44.4']),
  ('variant scenario 2', ('+119',), ['2.19', '119/100', '45.7'])]]
for label, args, expected in cases[N - 1]:
    check(label, run(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
control underdog['2.50', '3/2', '40.0']['2.50', '3/2', '40.0']Passed
control favourite['1.50', '1/2', '66.7']['1.50', '1/2', '66.7']Passed
boundary even money['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
boundary unsigned line['3.50', '5/2', '28.6']['3.50', '5/2', '28.6']Passed
boundary below minimuminvalidinvalidPassed
control half-up decimal['1.13', '1/8', '88.9']['1.13', '1/8', '88.9']Passed
regression: decimal rounding['1.30', '100/331', '76.8']['1.30', '100/331', '76.8']Passed
regression: decimal rounding['1.13', '1/8', '88.9']['1.13', '1/8', '88.9']Passed
variant scenario 1['2.00', '1/1', '50.0']['2.00', '1/1', '50.0']Passed
variant scenario 2['3.08', '52/25', '32.5']['3.08', '52/25', '32.5']Passed

SHA-256 / f44d7aba9efca5b192b7f57c2699a3ff155dac84ccf00047b3d8f8e1fc41240c

Verification & scope

Stipulated, bounded toy contract stated in the contract field; not a claim of conformance with any operator, exchange or regulator rule set. 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:50:30.421963+00:00.

Case digest / 6925455b8237421a1f5cd9e9d6cd747974a00bcdd9a8a207a7784341d23bc00f