Merge dev into main: v0.8.7 Kahan compensated summation

This commit is contained in:
Miha Kralj
2026-03-13 22:01:52 -07:00
79 changed files with 2923 additions and 2495 deletions
+15 -29
View File
@@ -34,15 +34,13 @@ public sealed class Bias : AbstractBase
private record struct State
{
public double Sum;
public double SumComp;
public double LastInput;
public double LastValidValue;
public int TickCount;
}
private State _state;
private State _p_state;
private const int ResyncInterval = 1000;
private const double Epsilon = 1e-10;
/// <summary>
@@ -158,20 +156,15 @@ public sealed class Bias : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void UpdateState(double val)
{
if (_buffer.Count == _buffer.Capacity)
{
_state.Sum -= _buffer.Oldest;
}
double removed = _buffer.Count == _buffer.Capacity ? _buffer.Oldest : 0.0;
// Kahan compensated summation
double delta = val - removed - _state.SumComp;
double newSum = _state.Sum + delta;
_state.SumComp = (newSum - _state.Sum) - delta;
_state.Sum = newSum;
_buffer.Add(val);
_state.Sum += val;
_state.TickCount++;
if (_buffer.IsFull && _state.TickCount >= ResyncInterval)
{
_state.TickCount = 0;
_state.Sum = _buffer.GetSpan().SumSIMD();
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -292,6 +285,7 @@ public sealed class Bias : AbstractBase
: bufferArray!.AsSpan(0, period);
double sum = 0;
double sumComp = 0;
double lastValid = double.NaN;
// Find first valid value
@@ -307,7 +301,6 @@ public sealed class Bias : AbstractBase
try
{
int bufferIndex = 0;
int tickCount = 0;
// Warmup phase
int warmupEnd = Math.Min(period, len);
@@ -344,8 +337,13 @@ public sealed class Bias : AbstractBase
val = lastValid;
}
// Kahan-compensated delta update for sum
double oldVal = buffer[bufferIndex];
sum = sum - oldVal + val;
double delta = val - oldVal;
double y = delta - sumComp;
double t = sum + y;
sumComp = (t - sum) - y;
sum = t;
buffer[bufferIndex] = val;
bufferIndex++;
@@ -356,18 +354,6 @@ public sealed class Bias : AbstractBase
double sma = sum / period;
output[i] = Math.Abs(sma) > Epsilon ? (val - sma) / sma : 0;
// Periodic resync for long sequences
tickCount++;
if (tickCount >= ResyncInterval)
{
tickCount = 0;
sum = 0;
for (int k = 0; k < period; k++)
{
sum += buffer[k];
}
}
}
}
finally
+6 -3
View File
@@ -148,14 +148,17 @@ public sealed class Prs : AbstractBase
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue Update(double baseValue, double compValue, bool isNew = true)
{
return Update(new TValue(DateTime.UtcNow, baseValue), new TValue(DateTime.UtcNow, compValue), isNew);
DateTime now = DateTime.UtcNow;
return Update(new TValue(now, baseValue), new TValue(now, compValue), isNew);
}
/// <remarks>Not supported for bi-input indicator. Use Update(baseValue, compValue) instead.</remarks>
/// <summary>Not supported for bi-input indicator. Use Update(baseValue, compValue) instead.</summary>
/// <remarks>PRS requires paired base/comparison inputs; single-input updates are invalid.</remarks>
public override TValue Update(TValue input, bool isNew = true)
{
throw new NotSupportedException("PRS requires two inputs (base and comparison). Use Update(baseValue, compValue).");
}
/// <remarks>Not supported for bi-input indicator. Use Calculate(baseSeries, compSeries, period) instead.</remarks>
/// <summary>Not supported for bi-input indicator. Use Calculate(baseSeries, compSeries, period) instead.</summary>
/// <remarks>PRS requires paired base/comparison series; single-series updates are invalid.</remarks>
public override TSeries Update(TSeries source)
{
throw new NotSupportedException("PRS requires two inputs. Use Batch(baseSeries, compSeries, period).");