mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 18:18:04 +00:00
style patterns
This commit is contained in:
@@ -272,4 +272,4 @@ public sealed class Beta : AbstractBase
|
||||
_sumRm2 = FusedMultiplyAdd(rm, rm, _sumRm2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,4 +166,4 @@ public class CmaIndicatorTests
|
||||
indicator.ProcessUpdate(new UpdateArgs(UpdateReason.NewBar));
|
||||
Assert.Equal(20.0, indicator.LinesSeries[0].GetValue(0), 1e-10); // CMA = (10+20+30)/3 = 20
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +401,9 @@ public class CmaTests
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
source[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
// Warm up
|
||||
Cma.Batch(source.AsSpan(), output.AsSpan());
|
||||
@@ -520,7 +522,10 @@ public class CmaTests
|
||||
public void Calculate_ReturnsCorrectResultsAndHotIndicator()
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 10; i++) series.Add(DateTime.UtcNow, i * 10);
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow, i * 10);
|
||||
}
|
||||
// 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
|
||||
|
||||
var (results, indicator) = Cma.Calculate(series);
|
||||
|
||||
@@ -96,7 +96,10 @@ public sealed class Cma : AbstractBase
|
||||
/// <param name="step">Time interval between values (not used for CMA)</param>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_state = default;
|
||||
@@ -162,7 +165,10 @@ public sealed class Cma : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -207,10 +213,15 @@ public sealed class Cma : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
double mean = 0;
|
||||
double lastValid = double.NaN;
|
||||
@@ -230,9 +241,13 @@ public sealed class Cma : AbstractBase
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
// M_n = M_(n-1) + alpha * delta using FMA for single-rounding precision
|
||||
double alpha = 1.0 / (i + 1);
|
||||
@@ -264,4 +279,4 @@ public sealed class Cma : AbstractBase
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,15 @@ public class CovarianceSimdTests
|
||||
{
|
||||
double x = dataX[i];
|
||||
double y = dataY[i];
|
||||
if (!double.IsFinite(x)) x = 0;
|
||||
if (!double.IsFinite(y)) y = 0;
|
||||
if (!double.IsFinite(x))
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
|
||||
var res = scalarCov.Update(x, y);
|
||||
Assert.Equal(res.Value, result.Values[i], precision: 9);
|
||||
|
||||
@@ -182,7 +182,9 @@ public sealed class Covariance : AbstractBase
|
||||
public static TSeries Calculate(TSeries sourceX, TSeries sourceY, int period, bool isPopulation = false)
|
||||
{
|
||||
if (sourceX.Count != sourceY.Count)
|
||||
{
|
||||
throw new ArgumentException("Source series must have the same length", nameof(sourceY));
|
||||
}
|
||||
|
||||
int len = sourceX.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -203,12 +205,20 @@ public sealed class Covariance : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> sourceX, ReadOnlySpan<double> sourceY, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (sourceX.Length != sourceY.Length || sourceX.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("All spans must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 2", nameof(period));
|
||||
}
|
||||
|
||||
int len = sourceX.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// SIMD overhead amortizes well for datasets >= 256 elements
|
||||
const int SimdThreshold = 256;
|
||||
@@ -242,8 +252,15 @@ public sealed class Covariance : AbstractBase
|
||||
{
|
||||
double x = sourceX[i];
|
||||
double y = sourceY[i];
|
||||
if (!double.IsFinite(x)) x = 0;
|
||||
if (!double.IsFinite(y)) y = 0;
|
||||
if (!double.IsFinite(x))
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
|
||||
sumX += x;
|
||||
sumY += y;
|
||||
@@ -270,8 +287,15 @@ public sealed class Covariance : AbstractBase
|
||||
{
|
||||
double x = sourceX[i];
|
||||
double y = sourceY[i];
|
||||
if (!double.IsFinite(x)) x = 0;
|
||||
if (!double.IsFinite(y)) y = 0;
|
||||
if (!double.IsFinite(x))
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
|
||||
double oldX = bufferX[bufferIndex];
|
||||
double oldY = bufferY[bufferIndex];
|
||||
@@ -283,7 +307,10 @@ public sealed class Covariance : AbstractBase
|
||||
bufferX[bufferIndex] = x;
|
||||
bufferY[bufferIndex] = y;
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period) bufferIndex = 0;
|
||||
if (bufferIndex >= period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
double n = period;
|
||||
double numerator = sumXY - (sumX * sumY) / n;
|
||||
@@ -357,7 +384,10 @@ public sealed class Covariance : AbstractBase
|
||||
|
||||
(double sumX, double sumY, double sumXY) = WarmupCovariance(period, len, isPopulation, ref srcXRef, ref srcYRef, ref outRef);
|
||||
|
||||
if (len <= period) return;
|
||||
if (len <= period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vInvN = Vector256.Create(invN);
|
||||
var vInvDenom = Vector256.Create(invDenom);
|
||||
@@ -452,13 +482,27 @@ public sealed class Covariance : AbstractBase
|
||||
{
|
||||
double x = Unsafe.Add(ref srcXRef, i);
|
||||
double y = Unsafe.Add(ref srcYRef, i);
|
||||
if (!double.IsFinite(x)) x = 0;
|
||||
if (!double.IsFinite(y)) y = 0;
|
||||
if (!double.IsFinite(x))
|
||||
{
|
||||
x = 0;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
y = 0;
|
||||
}
|
||||
|
||||
double oldX = Unsafe.Add(ref srcXRef, i - period);
|
||||
double oldY = Unsafe.Add(ref srcYRef, i - period);
|
||||
if (!double.IsFinite(oldX)) oldX = 0;
|
||||
if (!double.IsFinite(oldY)) oldY = 0;
|
||||
if (!double.IsFinite(oldX))
|
||||
{
|
||||
oldX = 0;
|
||||
}
|
||||
|
||||
if (!double.IsFinite(oldY))
|
||||
{
|
||||
oldY = 0;
|
||||
}
|
||||
|
||||
sumX = sumX - oldX + x;
|
||||
sumY = sumY - oldY + y;
|
||||
@@ -468,4 +512,4 @@ public sealed class Covariance : AbstractBase
|
||||
Unsafe.Add(ref outRef, i) = numerator * invDenom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,9 @@ public sealed class LinReg : AbstractBase
|
||||
public LinReg(int period, int offset = 0)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_offset = offset;
|
||||
@@ -262,7 +264,10 @@ public sealed class LinReg : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return new TSeries([], []);
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return new TSeries([], []);
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -332,12 +337,20 @@ public sealed class LinReg : AbstractBase
|
||||
public static void Calculate(ReadOnlySpan<double> source, Span<double> output, int period, int offset = 0, double initialLastValid = 0)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stack allocate for typical periods (most < 100)
|
||||
// ArrayPool for large periods to avoid stack overflow
|
||||
@@ -353,87 +366,95 @@ public sealed class LinReg : AbstractBase
|
||||
try
|
||||
{
|
||||
|
||||
double sum_y = 0;
|
||||
double sum_xy = 0;
|
||||
double lastValid = initialLastValid;
|
||||
int bufferIndex = 0;
|
||||
int count = 0;
|
||||
double sum_y = 0;
|
||||
double sum_xy = 0;
|
||||
double lastValid = initialLastValid;
|
||||
int bufferIndex = 0;
|
||||
int count = 0;
|
||||
|
||||
double full_sum_x = 0.5 * period * (period - 1);
|
||||
double full_sum_x2 = (period - 1.0) * period * (2.0 * period - 1.0) / 6.0;
|
||||
double full_denom = period * full_sum_x2 - full_sum_x * full_sum_x;
|
||||
double full_sum_x = 0.5 * period * (period - 1);
|
||||
double full_sum_x2 = (period - 1.0) * period * (2.0 * period - 1.0) / 6.0;
|
||||
double full_denom = period * full_sum_x2 - full_sum_x * full_sum_x;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
lastValid = val;
|
||||
else
|
||||
val = lastValid;
|
||||
|
||||
if (count < period)
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
buffer[count] = val;
|
||||
sum_y += val;
|
||||
count++;
|
||||
|
||||
sum_xy = 0;
|
||||
for (int j = 0; j < count; j++)
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
sum_xy = Math.FusedMultiplyAdd(count - 1 - j, buffer[j], sum_xy);
|
||||
}
|
||||
|
||||
if (count <= 1)
|
||||
{
|
||||
output[i] = val;
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
double n = count;
|
||||
double sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
double denom = n * sx2 - sx * sx;
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
if (Math.Abs(denom) < MinDenominator)
|
||||
if (count < period)
|
||||
{
|
||||
buffer[count] = val;
|
||||
sum_y += val;
|
||||
count++;
|
||||
|
||||
sum_xy = 0;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
sum_xy = Math.FusedMultiplyAdd(count - 1 - j, buffer[j], sum_xy);
|
||||
}
|
||||
|
||||
if (count <= 1)
|
||||
{
|
||||
output[i] = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
double m = Math.FusedMultiplyAdd(n, sum_xy, -sx * sum_y) / denom;
|
||||
double b = Math.FusedMultiplyAdd(-m, sx, sum_y) / n;
|
||||
output[i] = Math.FusedMultiplyAdd(-m, offset, b);
|
||||
double n = count;
|
||||
double sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
double denom = n * sx2 - sx * sx;
|
||||
|
||||
if (Math.Abs(denom) < MinDenominator)
|
||||
{
|
||||
output[i] = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
double m = Math.FusedMultiplyAdd(n, sum_xy, -sx * sum_y) / denom;
|
||||
double b = Math.FusedMultiplyAdd(-m, sx, sum_y) / n;
|
||||
output[i] = Math.FusedMultiplyAdd(-m, offset, b);
|
||||
}
|
||||
}
|
||||
|
||||
if (count == period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double oldest = buffer[bufferIndex];
|
||||
double prev_sum_y = sum_y;
|
||||
|
||||
if (count == period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
sum_xy = sum_xy + prev_sum_y - period * oldest;
|
||||
sum_y = sum_y - oldest + val;
|
||||
buffer[bufferIndex] = val;
|
||||
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
double m = Math.FusedMultiplyAdd(period, sum_xy, -full_sum_x * sum_y) / full_denom;
|
||||
double b = Math.FusedMultiplyAdd(-m, full_sum_x, sum_y) / period;
|
||||
output[i] = Math.FusedMultiplyAdd(-m, offset, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double oldest = buffer[bufferIndex];
|
||||
double prev_sum_y = sum_y;
|
||||
|
||||
sum_xy = sum_xy + prev_sum_y - period * oldest;
|
||||
sum_y = sum_y - oldest + val;
|
||||
buffer[bufferIndex] = val;
|
||||
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period)
|
||||
bufferIndex = 0;
|
||||
|
||||
double m = Math.FusedMultiplyAdd(period, sum_xy, -full_sum_x * sum_y) / full_denom;
|
||||
double b = Math.FusedMultiplyAdd(-m, full_sum_x, sum_y) / period;
|
||||
output[i] = Math.FusedMultiplyAdd(-m, offset, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rentedBuffer != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rentedBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,4 +469,4 @@ public sealed class LinReg : AbstractBase
|
||||
Intercept = 0;
|
||||
RSquared = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,14 +264,21 @@ public class MedianTests
|
||||
// Arrange
|
||||
int period = 5;
|
||||
double[] data = new double[20];
|
||||
for (int i = 0; i < data.Length; i++) data[i] = i;
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
data[i] = i;
|
||||
}
|
||||
|
||||
// Act
|
||||
double[] output = new double[data.Length];
|
||||
Median.Batch(data, output, period);
|
||||
|
||||
var series = new TSeries();
|
||||
for (int i = 0; i < data.Length; i++) series.Add(new TValue(DateTime.MinValue, data[i]));
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
series.Add(new TValue(DateTime.MinValue, data[i]));
|
||||
}
|
||||
|
||||
var batchSeries = Median.Batch(series, period);
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -86,7 +86,10 @@ public sealed class MedianValidationTests : IDisposable
|
||||
private static double CalculateMedian(List<double> sortedWindow)
|
||||
{
|
||||
int count = sortedWindow.Count;
|
||||
if (count == 0) return 0; // Or NaN
|
||||
if (count == 0)
|
||||
{
|
||||
return 0; // Or NaN
|
||||
}
|
||||
|
||||
int mid = count / 2;
|
||||
if (count % 2 != 0)
|
||||
|
||||
@@ -38,7 +38,9 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
public Median(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
@@ -76,7 +78,10 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
/// </summary>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_buffer.Clear();
|
||||
int warmupLength = Math.Min(source.Length, WarmupPeriod);
|
||||
@@ -151,7 +156,10 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -178,7 +186,10 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
// validCount = elements in sortedBuffer BEFORE insertion
|
||||
int validCount = _buffer.Count - 1;
|
||||
int index = Array.BinarySearch(_sortedBuffer, 0, validCount, value);
|
||||
if (index < 0) index = ~index;
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
if (index < validCount)
|
||||
{
|
||||
@@ -224,12 +235,20 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Always use ArrayPool to avoid CS8353 stackalloc escape issues
|
||||
double[] rentedSorted = ArrayPool<double>.Shared.Rent(period);
|
||||
@@ -266,7 +285,10 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
windowIdx = (windowIdx + 1) % period;
|
||||
|
||||
int newIndex = BinarySearchSpan(sortedBuffer, count, val);
|
||||
if (newIndex < 0) newIndex = ~newIndex;
|
||||
if (newIndex < 0)
|
||||
{
|
||||
newIndex = ~newIndex;
|
||||
}
|
||||
|
||||
if (newIndex < count)
|
||||
{
|
||||
@@ -300,11 +322,18 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
int mid = lo + ((hi - lo) >> 1);
|
||||
int cmp = span[mid].CompareTo(value);
|
||||
if (cmp == 0)
|
||||
{
|
||||
return mid;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi = mid - 1;
|
||||
}
|
||||
}
|
||||
return ~lo;
|
||||
}
|
||||
@@ -325,7 +354,10 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
/// </summary>
|
||||
public new void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_source != null)
|
||||
{
|
||||
@@ -335,4 +367,4 @@ public sealed class Median : AbstractBase, IDisposable
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,10 @@ public class SkewTests
|
||||
public void Reset_ClearsState()
|
||||
{
|
||||
var skew = new Skew(5);
|
||||
for (int i = 0; i < 5; i++) skew.Update(new TValue(DateTime.UtcNow, i));
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
skew.Update(new TValue(DateTime.UtcNow, i));
|
||||
}
|
||||
|
||||
skew.Reset();
|
||||
Assert.False(skew.IsHot);
|
||||
@@ -380,7 +383,10 @@ public class SkewTests
|
||||
// Create large dataset to trigger SIMD path (>= 256)
|
||||
int count = 1000;
|
||||
var data = new double[count];
|
||||
for (int i = 0; i < count; i++) data[i] = (double)i;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = (double)i;
|
||||
}
|
||||
|
||||
var series = new TSeries(new System.Collections.Generic.List<long>(new long[count]), new System.Collections.Generic.List<double>(data));
|
||||
|
||||
|
||||
+49
-11
@@ -104,7 +104,11 @@ public sealed class Skew : AbstractBase
|
||||
// Calculate 2nd moment (Variance)
|
||||
// m2 = Sum((x-mean)^2) / n = (SumSq - Sum^2/n) / n
|
||||
double m2Numerator = _sumSq - (_sum * _sum) / n;
|
||||
if (m2Numerator < Epsilon) m2Numerator = 0;
|
||||
if (m2Numerator < Epsilon)
|
||||
{
|
||||
m2Numerator = 0;
|
||||
}
|
||||
|
||||
double m2 = m2Numerator / n;
|
||||
|
||||
// Calculate 3rd moment
|
||||
@@ -143,7 +147,10 @@ public sealed class Skew : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -225,12 +232,20 @@ public sealed class Skew : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 3)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 3", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Try SIMD path for large, clean datasets
|
||||
// SIMD overhead amortizes well for datasets >= 256 elements
|
||||
@@ -260,7 +275,10 @@ public sealed class Skew : AbstractBase
|
||||
for (; i < warmupEnd; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (!double.IsFinite(val)) val = 0;
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = 0;
|
||||
}
|
||||
|
||||
sum += val;
|
||||
sumSq += val * val;
|
||||
@@ -275,10 +293,16 @@ public sealed class Skew : AbstractBase
|
||||
for (; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (!double.IsFinite(val)) val = 0;
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = 0;
|
||||
}
|
||||
|
||||
double oldVal = source[i - period];
|
||||
if (!double.IsFinite(oldVal)) oldVal = 0;
|
||||
if (!double.IsFinite(oldVal))
|
||||
{
|
||||
oldVal = 0;
|
||||
}
|
||||
|
||||
sum = sum - oldVal + val;
|
||||
sumSq = sumSq - (oldVal * oldVal) + (val * val);
|
||||
@@ -297,7 +321,11 @@ public sealed class Skew : AbstractBase
|
||||
for (int k = 0; k < period; k++)
|
||||
{
|
||||
double v = source[startIdx + k];
|
||||
if (!double.IsFinite(v)) v = 0;
|
||||
if (!double.IsFinite(v))
|
||||
{
|
||||
v = 0;
|
||||
}
|
||||
|
||||
recalcSum += v;
|
||||
recalcSumSq += v * v;
|
||||
recalcSumCu += v * v * v;
|
||||
@@ -315,13 +343,20 @@ public sealed class Skew : AbstractBase
|
||||
double mean = sum / n;
|
||||
|
||||
double m2Numerator = sumSq - (sum * sum) / n;
|
||||
if (m2Numerator < Epsilon) return 0;
|
||||
if (m2Numerator < Epsilon)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double m2 = m2Numerator / n;
|
||||
|
||||
double m3Numerator = sumCu - 3 * mean * sumSq + 2 * n * mean * mean * mean;
|
||||
double m3 = m3Numerator / n;
|
||||
|
||||
if (m2 <= Epsilon) return 0;
|
||||
if (m2 <= Epsilon)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
double g1 = m3 / (m2 * Math.Sqrt(m2));
|
||||
|
||||
@@ -367,7 +402,10 @@ public sealed class Skew : AbstractBase
|
||||
|
||||
WarmupSkew(period, isPopulation, ref srcRef, ref outRef, out double sum, out double sumSq, out double sumCu);
|
||||
|
||||
if (len <= period) return;
|
||||
if (len <= period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vInvN = Vector256.Create(invN);
|
||||
var vN = Vector256.Create(n);
|
||||
@@ -509,4 +547,4 @@ public sealed class Skew : AbstractBase
|
||||
Unsafe.Add(ref outRef, i) = CalculateSkewFromSums(sum, sumSq, sumCu, n, isPopulation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,16 @@ public sealed class StdDevValidationTests : IDisposable
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
if (disposing) _testData?.Dispose();
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
#region Skender Validation
|
||||
|
||||
@@ -57,7 +57,10 @@ public sealed class StdDev : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -189,4 +192,4 @@ public sealed class StdDev : AbstractBase
|
||||
data[i] = (val > 0) ? Math.Sqrt(val) : 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +392,9 @@ public class SumTests
|
||||
|
||||
var gbm = new GBM(startPrice: 100, mu: 0.05, sigma: 0.2, seed: 42);
|
||||
for (int i = 0; i < source.Length; i++)
|
||||
{
|
||||
source[i] = gbm.Next().Close;
|
||||
}
|
||||
|
||||
Sum.Batch(source.AsSpan(), output.AsSpan(), 100);
|
||||
|
||||
@@ -520,7 +522,9 @@ public class SumTests
|
||||
{
|
||||
var series = new TSeries();
|
||||
for (int i = 1; i <= 10; i++)
|
||||
{
|
||||
series.Add(DateTime.UtcNow, i * 10);
|
||||
}
|
||||
// 10, 20, 30, 40, 50, 60, 70, 80, 90, 100
|
||||
|
||||
var (results, indicator) = Sum.Calculate(series, 5);
|
||||
|
||||
@@ -26,9 +26,16 @@ public sealed class SumValidationTests : IDisposable
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
if (disposing) _testData?.Dispose();
|
||||
if (disposing)
|
||||
{
|
||||
_testData?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
+88
-59
@@ -67,7 +67,9 @@ public sealed class Sum : AbstractBase
|
||||
public Sum(int period)
|
||||
{
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
_period = period;
|
||||
_buffer = new RingBuffer(period);
|
||||
@@ -163,7 +165,10 @@ public sealed class Sum : AbstractBase
|
||||
/// </summary>
|
||||
public override void Prime(ReadOnlySpan<double> source, TimeSpan? step = null)
|
||||
{
|
||||
if (source.Length == 0) return;
|
||||
if (source.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset state
|
||||
_buffer.Clear();
|
||||
@@ -284,7 +289,10 @@ public sealed class Sum : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -325,12 +333,20 @@ public sealed class Sum : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period <= 0)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than 0", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateScalarCore(source, output, period);
|
||||
}
|
||||
@@ -378,72 +394,82 @@ public sealed class Sum : AbstractBase
|
||||
int tickCount = 0;
|
||||
|
||||
// Warmup phase
|
||||
int warmupEnd = Math.Min(period, len);
|
||||
for (int i = 0; i < warmupEnd; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
lastValid = val;
|
||||
else
|
||||
val = lastValid;
|
||||
int warmupEnd = Math.Min(period, len);
|
||||
for (int i = 0; i < warmupEnd; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
// Kahan-Babuška add
|
||||
double y = val - c;
|
||||
double t = sum + y;
|
||||
c = t - sum - y;
|
||||
sum = t;
|
||||
// Kahan-Babuška add
|
||||
double y = val - c;
|
||||
double t = sum + y;
|
||||
c = t - sum - y;
|
||||
sum = t;
|
||||
|
||||
double z = c - cc;
|
||||
double tt = sum + z;
|
||||
cc = tt - sum - z;
|
||||
sum = tt;
|
||||
double z = c - cc;
|
||||
double tt = sum + z;
|
||||
cc = tt - sum - z;
|
||||
sum = tt;
|
||||
|
||||
buffer[i] = val;
|
||||
output[i] = sum;
|
||||
}
|
||||
buffer[i] = val;
|
||||
output[i] = sum;
|
||||
}
|
||||
|
||||
// Main phase with sliding window
|
||||
for (int i = period; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
lastValid = val;
|
||||
else
|
||||
val = lastValid;
|
||||
// Main phase with sliding window
|
||||
for (int i = period; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (double.IsFinite(val))
|
||||
{
|
||||
lastValid = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
val = lastValid;
|
||||
}
|
||||
|
||||
double oldVal = buffer[bufferIndex];
|
||||
double oldVal = buffer[bufferIndex];
|
||||
|
||||
// Kahan-Babuška subtract old value
|
||||
double yS = -oldVal - c;
|
||||
double tS = sum + yS;
|
||||
c = tS - sum - yS;
|
||||
sum = tS;
|
||||
// Kahan-Babuška subtract old value
|
||||
double yS = -oldVal - c;
|
||||
double tS = sum + yS;
|
||||
c = tS - sum - yS;
|
||||
sum = tS;
|
||||
|
||||
double zS = c - cc;
|
||||
double ttS = sum + zS;
|
||||
cc = ttS - sum - zS;
|
||||
sum = ttS;
|
||||
double zS = c - cc;
|
||||
double ttS = sum + zS;
|
||||
cc = ttS - sum - zS;
|
||||
sum = ttS;
|
||||
|
||||
// Kahan-Babuška add new value
|
||||
double yA = val - c;
|
||||
double tA = sum + yA;
|
||||
c = tA - sum - yA;
|
||||
sum = tA;
|
||||
// Kahan-Babuška add new value
|
||||
double yA = val - c;
|
||||
double tA = sum + yA;
|
||||
c = tA - sum - yA;
|
||||
sum = tA;
|
||||
|
||||
double zA = c - cc;
|
||||
double ttA = sum + zA;
|
||||
cc = ttA - sum - zA;
|
||||
sum = ttA;
|
||||
double zA = c - cc;
|
||||
double ttA = sum + zA;
|
||||
cc = ttA - sum - zA;
|
||||
sum = ttA;
|
||||
|
||||
buffer[bufferIndex] = val;
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period)
|
||||
bufferIndex = 0;
|
||||
buffer[bufferIndex] = val;
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
output[i] = sum;
|
||||
output[i] = sum;
|
||||
|
||||
// Periodic resync for long sequences
|
||||
tickCount++;
|
||||
// Periodic resync for long sequences
|
||||
tickCount++;
|
||||
if (tickCount >= ResyncInterval)
|
||||
{
|
||||
tickCount = 0;
|
||||
@@ -468,7 +494,10 @@ public sealed class Sum : AbstractBase
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (bufferArray != null) ArrayPool<double>.Shared.Return(bufferArray);
|
||||
if (bufferArray != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(bufferArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,4 +511,4 @@ public sealed class Sum : AbstractBase
|
||||
_p_state = default;
|
||||
Last = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,9 @@ public sealed class VarianceIndicator : Indicator, IWatchlistIndicator
|
||||
protected override void OnUpdate(UpdateArgs args)
|
||||
{
|
||||
if (args.Reason != UpdateReason.NewBar && args.Reason != UpdateReason.HistoricalBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = this.HistoricalData[this.Count - 1, SeekOriginHistory.Begin];
|
||||
double value = _priceSelector(item);
|
||||
|
||||
@@ -46,20 +46,20 @@ public class VarianceTests
|
||||
{
|
||||
// Use simple known values for easier debugging
|
||||
var variance = new Variance(3);
|
||||
|
||||
|
||||
// Add 3 values: 1, 2, 3
|
||||
variance.Update(new TValue(DateTime.UtcNow, 1), isNew: true);
|
||||
variance.Update(new TValue(DateTime.UtcNow, 2), isNew: true);
|
||||
var originalResult = variance.Update(new TValue(DateTime.UtcNow, 3), isNew: true);
|
||||
|
||||
|
||||
double expectedVariance = originalResult.Value; // Variance of [1,2,3]
|
||||
|
||||
|
||||
// Now correct the 3rd value to 10 (isNew=false)
|
||||
variance.Update(new TValue(DateTime.UtcNow, 10), isNew: false);
|
||||
|
||||
|
||||
// Correct back to original value 3 (isNew=false)
|
||||
var restoredResult = variance.Update(new TValue(DateTime.UtcNow, 3), isNew: false);
|
||||
|
||||
|
||||
// Should match original variance
|
||||
Assert.Equal(expectedVariance, restoredResult.Value, 1e-10);
|
||||
}
|
||||
@@ -351,7 +351,10 @@ public class VarianceTests
|
||||
// Create large dataset to trigger SIMD path (>= 256)
|
||||
const int count = 1000;
|
||||
var data = new double[count];
|
||||
for (int i = 0; i < count; i++) data[i] = (double)i;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
data[i] = (double)i;
|
||||
}
|
||||
|
||||
var series = new TSeries(new System.Collections.Generic.List<long>(new long[count]), new System.Collections.Generic.List<double>(data));
|
||||
|
||||
@@ -714,4 +717,4 @@ public class VarianceTests
|
||||
Assert.Equal(0, output[0]); // N=1
|
||||
Assert.Equal(50, output[1]); // Var([10,20]) = 50
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,10 @@ public sealed class Variance : AbstractBase
|
||||
double numerator = _sumSq - (_buffer.Sum * _buffer.Sum) / n;
|
||||
|
||||
// Handle floating point noise
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
double denominator = _isPopulation ? n : (n - 1);
|
||||
variance = numerator / denominator;
|
||||
@@ -113,7 +116,10 @@ public sealed class Variance : AbstractBase
|
||||
|
||||
public override TSeries Update(TSeries source)
|
||||
{
|
||||
if (source.Count == 0) return [];
|
||||
if (source.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
int len = source.Count;
|
||||
var t = new List<long>(len);
|
||||
@@ -181,12 +187,20 @@ public sealed class Variance : AbstractBase
|
||||
public static void Batch(ReadOnlySpan<double> source, Span<double> output, int period, bool isPopulation = false)
|
||||
{
|
||||
if (source.Length != output.Length)
|
||||
{
|
||||
throw new ArgumentException("Source and output must have the same length", nameof(output));
|
||||
}
|
||||
|
||||
if (period < 2)
|
||||
{
|
||||
throw new ArgumentException("Period must be greater than or equal to 2", nameof(period));
|
||||
}
|
||||
|
||||
int len = source.Length;
|
||||
if (len == 0) return;
|
||||
if (len == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Try SIMD path for large, clean datasets
|
||||
const int SimdThreshold = 256;
|
||||
@@ -237,7 +251,10 @@ public sealed class Variance : AbstractBase
|
||||
for (; i < warmupEnd; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (!double.IsFinite(val)) val = 0; // Fallback
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = 0; // Fallback
|
||||
}
|
||||
|
||||
sum += val;
|
||||
sumSq = Math.FusedMultiplyAdd(val, val, sumSq);
|
||||
@@ -247,7 +264,11 @@ public sealed class Variance : AbstractBase
|
||||
if (n > 1)
|
||||
{
|
||||
double numerator = sumSq - (sum * sum) / n;
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
double denominator = isPopulation ? n : (n - 1);
|
||||
output[i] = numerator / denominator;
|
||||
}
|
||||
@@ -262,7 +283,10 @@ public sealed class Variance : AbstractBase
|
||||
for (; i < len; i++)
|
||||
{
|
||||
double val = source[i];
|
||||
if (!double.IsFinite(val)) val = 0; // Fallback
|
||||
if (!double.IsFinite(val))
|
||||
{
|
||||
val = 0; // Fallback
|
||||
}
|
||||
|
||||
double oldVal = buffer[bufferIndex];
|
||||
|
||||
@@ -272,11 +296,18 @@ public sealed class Variance : AbstractBase
|
||||
|
||||
buffer[bufferIndex] = val;
|
||||
bufferIndex++;
|
||||
if (bufferIndex >= period) bufferIndex = 0;
|
||||
if (bufferIndex >= period)
|
||||
{
|
||||
bufferIndex = 0;
|
||||
}
|
||||
|
||||
double n = period;
|
||||
double numerator = sumSq - (sum * sum) / n;
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
double denominator = isPopulation ? n : (n - 1);
|
||||
output[i] = numerator / denominator;
|
||||
|
||||
@@ -305,7 +336,11 @@ public sealed class Variance : AbstractBase
|
||||
if (n > 1)
|
||||
{
|
||||
double num = sumSq - (sum * sum) / n;
|
||||
if (num < 0) num = 0;
|
||||
if (num < 0)
|
||||
{
|
||||
num = 0;
|
||||
}
|
||||
|
||||
double den = isPopulation ? n : (n - 1);
|
||||
Unsafe.Add(ref outRef, i) = num / den;
|
||||
}
|
||||
@@ -330,7 +365,10 @@ public sealed class Variance : AbstractBase
|
||||
|
||||
WarmupVariance(period, isPopulation, ref srcRef, ref outRef, out double sum, out double sumSq);
|
||||
|
||||
if (len <= period) return;
|
||||
if (len <= period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vInvN = Vector512.Create(invN);
|
||||
var vInvDenom = Vector512.Create(invDenom);
|
||||
@@ -420,7 +458,11 @@ public sealed class Variance : AbstractBase
|
||||
sumSq = Math.FusedMultiplyAdd(val, val, sumSq);
|
||||
|
||||
double numerator = sumSq - sum * sum * invN;
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
Unsafe.Add(ref outRef, i) = numerator * invDenom;
|
||||
}
|
||||
}
|
||||
@@ -439,7 +481,10 @@ public sealed class Variance : AbstractBase
|
||||
|
||||
WarmupVariance(period, isPopulation, ref srcRef, ref outRef, out double sum, out double sumSq);
|
||||
|
||||
if (len <= period) return;
|
||||
if (len <= period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vInvN = Vector128.Create(invN);
|
||||
var vInvDenom = Vector128.Create(invDenom);
|
||||
@@ -517,7 +562,11 @@ public sealed class Variance : AbstractBase
|
||||
sumSq = Math.FusedMultiplyAdd(val, val, sumSq);
|
||||
|
||||
double numerator = sumSq - sum * sum * invN;
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
Unsafe.Add(ref outRef, i) = numerator * invDenom;
|
||||
}
|
||||
}
|
||||
@@ -536,7 +585,10 @@ public sealed class Variance : AbstractBase
|
||||
|
||||
WarmupVariance(period, isPopulation, ref srcRef, ref outRef, out double sum, out double sumSq);
|
||||
|
||||
if (len <= period) return;
|
||||
if (len <= period)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var vInvN = Vector256.Create(invN);
|
||||
var vInvDenom = Vector256.Create(invDenom);
|
||||
@@ -634,8 +686,12 @@ public sealed class Variance : AbstractBase
|
||||
sumSq = Math.FusedMultiplyAdd(val, val, sumSq);
|
||||
|
||||
double numerator = sumSq - sum * sum * invN;
|
||||
if (numerator < 0) numerator = 0;
|
||||
if (numerator < 0)
|
||||
{
|
||||
numerator = 0;
|
||||
}
|
||||
|
||||
Unsafe.Add(ref outRef, i) = numerator * invDenom;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user