fix(sma, bollinger): periodic recompute to bound long-stream drift (R7, L2-Rust)
`Sma` and `BollingerBands` both maintained their running `sum` (and `sum_sq` for Bollinger) with a single-subtract incremental update. That is correct in exact arithmetic, but in f64 the sequence `sum -= old; sum += new` on long streams with alternating large/small magnitudes can accumulate catastrophic-cancellation error. Bollinger's existing `.max(0.0)` clamp on the computed variance was a band-aid for the same root cause — the drift had already driven the running variance below zero. The fix: every `16 · period` finite updates, reseed `sum` (and `sum_sq` for Bollinger) from the live window. Amortised cost stays at O(1) — `O(period)` work amortised over `O(period)` updates — and the reseed strategy is named after the constant `RECOMPUTE_EVERY` so the intention is clear at the call site. Behaviour is unchanged on inputs that did not drift to begin with (every existing test still passes, including `batch_equals_streaming` and the SMA proptest). Two new stress tests (`long_stream_drift_stays_bounded` in each module) feed a magnitude-alternating stream for `5 · RECOMPUTE_EVERY · period` updates and assert the reported value tracks a fresh from-scratch computation over the live window to within tight tolerance — these would have failed without the reseed on Bollinger's `sum_sq`. The misleading `sma.rs` comment that claimed drift was already bounded by recomputing the sum after each pop is rewritten to describe the actual reseed strategy (audit finding L2-Rust).
This commit is contained in:
@@ -24,6 +24,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
site makes the invariant explicit.
|
||||
|
||||
### Changed
|
||||
- `Sma` and `BollingerBands` now reseed their incremental `sum` (and `sum_sq`
|
||||
for Bollinger) from the live window every `16 · period` finite updates,
|
||||
capping floating-point drift on long-running streams (audit findings R7 and
|
||||
L2-Rust). Previously the incremental single-subtract `sum -= old` could
|
||||
accumulate catastrophic-cancellation error on streams with alternating
|
||||
large/small magnitudes; the misleading `sma.rs` comment that claimed the
|
||||
drift was already bounded "by recomputing the sum after each pop" is
|
||||
replaced with an accurate description of the new reseed strategy. Amortised
|
||||
cost stays at O(1) (`O(period)` work amortised over `O(period)` updates),
|
||||
values are bit-identical on inputs that did not drift to begin with, and
|
||||
two new `long_stream_drift_stays_bounded` tests stress the recompute by
|
||||
alternating `1e9` / `1.0` (SMA) and `1e6` / `1.0` (Bollinger) for several
|
||||
recompute cycles and verify the reported values track a fresh from-scratch
|
||||
computation over the live window.
|
||||
- `LinearRegression`, `LinRegSlope` and `LinRegAngle` (via composition over
|
||||
`LinRegSlope`) now run their rolling ordinary-least-squares fit
|
||||
**incrementally** in O(1) per update (audit finding R2). Previously every
|
||||
|
||||
Reference in New Issue
Block a user