FA-91481 / Digital signal filters / Open access
Q15 smoother divides with truncation toward zero · case 01
Negative updates round differently from positive ones, producing an asymmetric limit cycle.
ROOT CAUSE
The shift is replaced with int(... / 32768), which truncates toward zero instead of flooring.
VERIFIED REPAIR
Use an arithmetic right shift (floor) after adding the half-LSB offset.
Unsuccessful approach: The attempted repair uses round(product / 32768), whose banker ties differ from round-half-up.
Case contract
Input [alpha, samples]; alpha is Q15 in [0, 32768] ("bad-alpha" otherwise). Inputs are saturated to int16; y += ((x - y) * alpha + 2**14) >> 15 (round half up on the Q15 product via arithmetic shift), starting at y = 0. Return the integer outputs.
Why this case matters
Fixed-point exponential smoothers run on microcontrollers; rounding direction and saturation slips cause bias and limit cycles.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
alpha, xs = x
if not 0 <= alpha <= 32768:
return 'bad-alpha'
y = 0
out = []
for v in xs:
v = max(-32768, min(32767, v))
y = y + int(((v - y) * alpha + (1 << 14)) / 32768)
out.append(y)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: alpha one', [32768, [100, -5, 7]], [100, -5, 7]], ['regression: input beyond range', [16384, [40000, 40000, -40000]], [16384, 24576, -4096]], ['repair check: small negative tie', [16384, [-3, 0, 0]], [-1, 0, 0]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]]], [['regression: random smoother 2', [32768, [-33, 20874, -43, 6, -28]], [-33, 20874, -43, 6, -28]], ['regression: random smoother 3', [32768, [8, 9547, 10, -20745, -49, 4]], [8, 9547, 10, -20745, -49, 4]], ['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]]], [['regression: random smoother 6', [8192, [16294, 2, -779, 14009]], [4074, 3056, 2097, 5075]], ['regression: random smoother 7', [1000, [-8425, 39]], [-257, -248]], ['regression: random smoother 20', [16384, [6, -12155, -29083, 31]], [3, -6076, -17579, -8774]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]]], [['regression: random smoother 9', [32768, [-37759, -22144, -45]], [-32768, -22144, -45]], ['regression: random smoother 10', [32768, [-12, -3127, -26, -45, -25]], [-12, -3127, -26, -45, -25]], ['regression: random smoother 36', [16384, [19291, 32, 32408, 5]], [9646, 4839, 18624, 9315]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]]], [['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['regression: random smoother 13', [8192, [-8, -27561]], [-2, -6892]], ['regression: random smoother 40', [16384, [32955, 23, 14565, 33, 25313, 28, 7111]], [16384, 8204, 11385, 5709, 15511, 7770, 7441]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]]]]
for label, args, expected in fixtures[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: alpha one | [100, -4, 7] | [100, -5, 7] | Failed |
| regression: input beyond range | [16384, 24576, -4095] | [16384, 24576, -4096] | Failed |
| repair check: small negative tie | [-1, 0, 0] | [-1, 0, 0] | Passed |
| control: negative step | [0, 0, 0] | [0, 0, 0] | Passed |
| control: alpha zero | [0, 0] | [0, 0] | Passed |
| control: bad alpha | bad-alpha | bad-alpha | Passed |
| control: random smoother 1 | [10, 3449] | [10, 3449] | Passed |
SHA-256 / ca59439fe1694e4edab02de8724d5eda645e93600ab79d83782c30b0eb1becab
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
alpha, xs = x
if not 0 <= alpha <= 32768:
return 'bad-alpha'
y = 0
out = []
for v in xs:
v = max(-32768, min(32767, v))
y = y + round((v - y) * alpha / 32768)
out.append(y)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: alpha one', [32768, [100, -5, 7]], [100, -5, 7]], ['regression: input beyond range', [16384, [40000, 40000, -40000]], [16384, 24576, -4096]], ['repair check: small negative tie', [16384, [-3, 0, 0]], [-1, 0, 0]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]]], [['regression: random smoother 2', [32768, [-33, 20874, -43, 6, -28]], [-33, 20874, -43, 6, -28]], ['regression: random smoother 3', [32768, [8, 9547, 10, -20745, -49, 4]], [8, 9547, 10, -20745, -49, 4]], ['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]]], [['regression: random smoother 6', [8192, [16294, 2, -779, 14009]], [4074, 3056, 2097, 5075]], ['regression: random smoother 7', [1000, [-8425, 39]], [-257, -248]], ['regression: random smoother 20', [16384, [6, -12155, -29083, 31]], [3, -6076, -17579, -8774]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]]], [['regression: random smoother 9', [32768, [-37759, -22144, -45]], [-32768, -22144, -45]], ['regression: random smoother 10', [32768, [-12, -3127, -26, -45, -25]], [-12, -3127, -26, -45, -25]], ['regression: random smoother 36', [16384, [19291, 32, 32408, 5]], [9646, 4839, 18624, 9315]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]]], [['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['regression: random smoother 13', [8192, [-8, -27561]], [-2, -6892]], ['regression: random smoother 40', [16384, [32955, 23, 14565, 33, 25313, 28, 7111]], [16384, 8204, 11385, 5709, 15511, 7770, 7441]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]]]]
for label, args, expected in fixtures[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: alpha one | [100, -5, 7] | [100, -5, 7] | Passed |
| regression: input beyond range | [16384, 24576, -4096] | [16384, 24576, -4096] | Passed |
| repair check: small negative tie | [-2, -1, -1] | [-1, 0, 0] | Failed |
| control: negative step | [0, 0, 0] | [0, 0, 0] | Passed |
| control: alpha zero | [0, 0] | [0, 0] | Passed |
| control: bad alpha | bad-alpha | bad-alpha | Passed |
| control: random smoother 1 | [10, 3449] | [10, 3449] | Passed |
SHA-256 / 24f95721ccdba06ffb214368811c0ea3edf4005b85d0aa6b3ce9ba6822169c2e
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(x):
alpha, xs = x
if not 0 <= alpha <= 32768:
return 'bad-alpha'
y = 0
out = []
for v in xs:
v = max(-32768, min(32767, v))
y = y + (((v - y) * alpha + (1 << 14)) >> 15)
out.append(y)
return out
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[['regression: alpha one', [32768, [100, -5, 7]], [100, -5, 7]], ['regression: input beyond range', [16384, [40000, 40000, -40000]], [16384, 24576, -4096]], ['repair check: small negative tie', [16384, [-3, 0, 0]], [-1, 0, 0]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]]], [['regression: random smoother 2', [32768, [-33, 20874, -43, 6, -28]], [-33, 20874, -43, 6, -28]], ['regression: random smoother 3', [32768, [8, 9547, 10, -20745, -49, 4]], [8, 9547, 10, -20745, -49, 4]], ['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]]], [['regression: random smoother 6', [8192, [16294, 2, -779, 14009]], [4074, 3056, 2097, 5075]], ['regression: random smoother 7', [1000, [-8425, 39]], [-257, -248]], ['regression: random smoother 20', [16384, [6, -12155, -29083, 31]], [3, -6076, -17579, -8774]], ['control: alpha zero', [0, [5, 5]], [0, 0]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]]], [['regression: random smoother 9', [32768, [-37759, -22144, -45]], [-32768, -22144, -45]], ['regression: random smoother 10', [32768, [-12, -3127, -26, -45, -25]], [-12, -3127, -26, -45, -25]], ['regression: random smoother 36', [16384, [19291, 32, 32408, 5]], [9646, 4839, 18624, 9315]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]], ['control: random smoother 49', [24576, [-17286, -45, 28008]], [-12964, -3275, 20187]], ['control: negative step', [16384, [-1, -1, -1]], [0, 0, 0]], ['control: alpha zero', [0, [5, 5]], [0, 0]]], [['regression: random smoother 12', [16384, [-15862, 2530, 35915]], [-7931, -2700, 15034]], ['regression: random smoother 13', [8192, [-8, -27561]], [-2, -6892]], ['regression: random smoother 40', [16384, [32955, 23, 14565, 33, 25313, 28, 7111]], [16384, 8204, 11385, 5709, 15511, 7770, 7441]], ['control: bad alpha', [40000, [1]], 'bad-alpha'], ['control: random smoother 1', [8192, [38, 13765]], [10, 3449]], ['control: random smoother 5', [32768, [5, 7014, 39535]], [5, 7014, 32767]], ['control: random smoother 39', [8192, [14896, 30856, 13380, 32942]], [3724, 10507, 11225, 16611]]]]
for label, args, expected in fixtures[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: alpha one | [100, -5, 7] | [100, -5, 7] | Passed |
| regression: input beyond range | [16384, 24576, -4096] | [16384, 24576, -4096] | Passed |
| repair check: small negative tie | [-1, 0, 0] | [-1, 0, 0] | Passed |
| control: negative step | [0, 0, 0] | [0, 0, 0] | Passed |
| control: alpha zero | [0, 0] | [0, 0] | Passed |
| control: bad alpha | bad-alpha | bad-alpha | Passed |
| control: random smoother 1 | [10, 3449] | [10, 3449] | Passed |
SHA-256 / d345f70b1b6295c583329f8a7b4557e0aeec8e540cf3717a5911a4fe0cbb3eee
Verification & scope
A deterministic bounded teaching model with a stipulated toy contract; exact rational arithmetic or fixed-decimal rounding keeps outputs strict JSON. It is not a production DSP library and claims no standards conformance. 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:51:36.309721+00:00.
Case digest / 3cfd57df5c719df69903863bc928972069da59cf74e48adfcb45b48b8b3c8b63