955 lines
33 KiB
C#
955 lines
33 KiB
C#
using System.Buffers;
|
|
using System.Runtime.Intrinsics;
|
|
using System.Runtime.Intrinsics.X86;
|
|
|
|
namespace QWERTYkez.Mensura.Extensions;
|
|
|
|
public static partial class AggregateUnitExtensions
|
|
{
|
|
// Sum Average Max Min (не nullable) ==========================================
|
|
|
|
|
|
// === ReadOnlySpan === SIMD
|
|
public static T Sum<T>(this ReadOnlySpan<T> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty) return default;
|
|
|
|
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
|
double sum = 0;
|
|
int i = 0;
|
|
|
|
// 1. ПУТЬ AVX2 (Intel/AMD) — Самый быстрый, обрабатывает по 4 элемента через Unsafe
|
|
if (Avx2.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vSum = Vector256<double>.Zero;
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vSum = Avx2.Add(vSum, v);
|
|
}
|
|
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
|
}
|
|
// 2. ПУТЬ AVX (Старые x64 CPU) — Чуть медленнее AVX2, но тоже по 4 элемента
|
|
else if (Avx.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vSum = Vector256<double>.Zero;
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vSum = Avx.Add(vSum, v);
|
|
}
|
|
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
|
}
|
|
// 3. ПУТЬ VECTOR (ARM64 / Apple Silicon / SSE) — Кроссплатформенный SIMD fallback
|
|
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
|
{
|
|
int vCount = Vector<double>.Count;
|
|
Vector<double> vSum = Vector<double>.Zero;
|
|
|
|
for (; i <= values.Length - vCount; i += vCount)
|
|
{
|
|
var v = new Vector<double>(values.Slice(i, vCount));
|
|
vSum += v;
|
|
}
|
|
|
|
for (int j = 0; j < vCount; j++)
|
|
{
|
|
sum += vSum[j];
|
|
}
|
|
}
|
|
|
|
// Хвост массива (или обычный расчет, если SIMD вообще нет)
|
|
for (; i < values.Length; i++)
|
|
{
|
|
sum += values[i];
|
|
}
|
|
|
|
return Unsafe.As<double, T>(ref sum);
|
|
}
|
|
public static T Average<T>(this ReadOnlySpan<T> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
// Если коллекция пустая, возвращаем структуру, содержащую double.NaN
|
|
if (units.IsEmpty)
|
|
{
|
|
double nan = double.NaN;
|
|
return Unsafe.As<double, T>(ref nan);
|
|
}
|
|
|
|
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
|
double sum = 0;
|
|
int i = 0;
|
|
|
|
// 1. ПУТЬ AVX2 (Intel/AMD)
|
|
if (Avx2.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vSum = Vector256<double>.Zero;
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vSum = Avx2.Add(vSum, v);
|
|
}
|
|
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
|
}
|
|
// 2. ПУТЬ AVX (Старые x64 CPU)
|
|
else if (Avx.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vSum = Vector256<double>.Zero;
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vSum = Avx.Add(vSum, v);
|
|
}
|
|
sum = vSum.GetElement(0) + vSum.GetElement(1) + vSum.GetElement(2) + vSum.GetElement(3);
|
|
}
|
|
// 3. ПУТЬ VECTOR (ARM64 / SSE)
|
|
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
|
{
|
|
int vCount = Vector<double>.Count;
|
|
Vector<double> vSum = Vector<double>.Zero;
|
|
|
|
for (; i <= values.Length - vCount; i += vCount)
|
|
{
|
|
var v = new Vector<double>(values.Slice(i, vCount));
|
|
vSum += v;
|
|
}
|
|
|
|
for (int j = 0; j < vCount; j++)
|
|
{
|
|
sum += vSum[j];
|
|
}
|
|
}
|
|
|
|
// 4. Довычисление хвоста
|
|
for (; i < values.Length; i++)
|
|
{
|
|
sum += values[i];
|
|
}
|
|
|
|
// Находим среднее значение
|
|
double average = sum / values.Length;
|
|
|
|
// Упаковываем double обратно в структуру T (zero-cost)
|
|
return Unsafe.As<double, T>(ref average);
|
|
}
|
|
public static T Max<T>(this ReadOnlySpan<T> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty)
|
|
{
|
|
double minVal = double.MinValue;
|
|
return Unsafe.As<double, T>(ref minVal);
|
|
}
|
|
|
|
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
|
double max = double.MinValue;
|
|
int i = 0;
|
|
|
|
if (Avx2.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vMax = Vector256.Create(double.MinValue);
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vMax = Avx2.Max(vMax, v);
|
|
}
|
|
max = Math.Max(Math.Max(vMax.GetElement(0), vMax.GetElement(1)), Math.Max(vMax.GetElement(2), vMax.GetElement(3)));
|
|
}
|
|
else if (Avx.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vMax = Vector256.Create(double.MinValue);
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vMax = Avx.Max(vMax, v);
|
|
}
|
|
max = Math.Max(Math.Max(vMax.GetElement(0), vMax.GetElement(1)), Math.Max(vMax.GetElement(2), vMax.GetElement(3)));
|
|
}
|
|
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
|
{
|
|
int vCount = Vector<double>.Count;
|
|
var vMax = new Vector<double>(double.MinValue);
|
|
|
|
for (; i <= values.Length - vCount; i += vCount)
|
|
{
|
|
var v = new Vector<double>(values.Slice(i, vCount));
|
|
vMax = Vector.Max(vMax, v);
|
|
}
|
|
|
|
for (int j = 0; j < vCount; j++)
|
|
{
|
|
if (vMax[j] > max) max = vMax[j];
|
|
}
|
|
}
|
|
|
|
// Довычисление хвоста
|
|
for (; i < values.Length; i++)
|
|
{
|
|
if (values[i] > max) max = values[i];
|
|
}
|
|
|
|
return Unsafe.As<double, T>(ref max);
|
|
}
|
|
public static T Min<T>(this ReadOnlySpan<T> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty)
|
|
{
|
|
double maxVal = double.MaxValue;
|
|
return Unsafe.As<double, T>(ref maxVal);
|
|
}
|
|
|
|
ReadOnlySpan<double> values = MemoryMarshal.Cast<T, double>(units);
|
|
double min = double.MaxValue;
|
|
int i = 0;
|
|
|
|
if (Avx2.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vMin = Vector256.Create(double.MaxValue);
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vMin = Avx2.Min(vMin, v);
|
|
}
|
|
min = Math.Min(Math.Min(vMin.GetElement(0), vMin.GetElement(1)), Math.Min(vMin.GetElement(2), vMin.GetElement(3)));
|
|
}
|
|
else if (Avx.IsSupported && values.Length >= 4)
|
|
{
|
|
Vector256<double> vMin = Vector256.Create(double.MaxValue);
|
|
ref double start = ref MemoryMarshal.GetReference(values);
|
|
|
|
for (; i <= values.Length - 4; i += 4)
|
|
{
|
|
ref double currentRef = ref Unsafe.Add(ref start, i);
|
|
var v = Unsafe.As<double, Vector256<double>>(ref currentRef);
|
|
vMin = Avx.Min(vMin, v);
|
|
}
|
|
min = Math.Min(Math.Min(vMin.GetElement(0), vMin.GetElement(1)), Math.Min(vMin.GetElement(2), vMin.GetElement(3)));
|
|
}
|
|
else if (Vector.IsHardwareAccelerated && values.Length >= Vector<double>.Count)
|
|
{
|
|
int vCount = Vector<double>.Count;
|
|
var vMin = new Vector<double>(double.MaxValue);
|
|
|
|
for (; i <= values.Length - vCount; i += vCount)
|
|
{
|
|
var v = new Vector<double>(values.Slice(i, vCount));
|
|
vMin = Vector.Min(vMin, v);
|
|
}
|
|
|
|
for (int j = 0; j < vCount; j++)
|
|
{
|
|
if (vMin[j] < min) min = vMin[j];
|
|
}
|
|
}
|
|
|
|
// Довычисление хвоста
|
|
for (; i < values.Length; i++)
|
|
{
|
|
if (values[i] < min) min = values[i];
|
|
}
|
|
|
|
return Unsafe.As<double, T>(ref min);
|
|
}
|
|
|
|
// === List<T> ===
|
|
public static T Sum<T>(this List<T> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Sum();
|
|
}
|
|
public static T Average<T>(this List<T> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Average();
|
|
}
|
|
public static T Max<T>(this List<T> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Max();
|
|
}
|
|
public static T Min<T>(this List<T> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Min();
|
|
}
|
|
|
|
// === ICollection<T> ===
|
|
public static T Sum<T>(this ICollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return default;
|
|
if (collection is T[] array) return array.Sum();
|
|
if (collection is List<T> list) return list.Sum();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0); // Встроенный CopyTo работает быстрее, чем ручной foreach
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Sum();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Average<T>(this ICollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
|
if (collection is T[] array) return array.Average();
|
|
if (collection is List<T> list) return list.Average();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Average();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Max<T>(this ICollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
|
if (collection is T[] array) return array.Max();
|
|
if (collection is List<T> list) return list.Max();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Max();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Min<T>(this ICollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
|
if (collection is T[] array) return array.Min();
|
|
if (collection is List<T> list) return list.Min();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Min();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
|
|
// === IReadOnlyCollection<T> ===
|
|
public static T Sum<T>(this IReadOnlyCollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return default;
|
|
if (collection is T[] array) return array.Sum();
|
|
if (collection is List<T> list) return list.Sum();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Sum();
|
|
}
|
|
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Average<T>(this IReadOnlyCollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
|
if (collection is T[] array) return array.Average();
|
|
if (collection is List<T> list) return list.Average();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Average();
|
|
}
|
|
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Max<T>(this IReadOnlyCollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
|
if (collection is T[] array) return array.Max();
|
|
if (collection is List<T> list) return list.Max();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Max();
|
|
}
|
|
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Min<T>(this IReadOnlyCollection<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
|
if (collection is T[] array) return array.Min();
|
|
if (collection is List<T> list) return list.Min();
|
|
|
|
T[] sharedArray = ArrayPool<T>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T>(sharedArray, 0, collection.Count).Min();
|
|
}
|
|
finally { ArrayPool<T>.Shared.Return(sharedArray); }
|
|
}
|
|
|
|
|
|
// === IEnumerable<T> ===
|
|
public static T Sum<T>(this IEnumerable<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return default;
|
|
|
|
// Быстрый SIMD-путь для готовых коллекций (0 аллокаций)
|
|
if (collection is T[] array) return array.Sum();
|
|
if (collection is List<T> list) return list.Sum();
|
|
if (collection is ICollection<T> col) return col.Sum();
|
|
if (collection is IReadOnlyCollection<T> roc) return roc.Sum();
|
|
|
|
// Медленный путь для yield return: считаем на лету без буферов
|
|
double sum = 0;
|
|
bool hasElements = false;
|
|
|
|
foreach (var item in collection)
|
|
{
|
|
sum += item.ToDouble();
|
|
hasElements = true;
|
|
}
|
|
|
|
return hasElements ? sum.ToUnit<T>() : default;
|
|
}
|
|
public static T Average<T>(this IEnumerable<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.NaN.ToUnit<T>();
|
|
|
|
if (collection is T[] array) return array.Average();
|
|
if (collection is List<T> list) return list.Average();
|
|
if (collection is ICollection<T> col) return col.Average();
|
|
if (collection is IReadOnlyCollection<T> roc) return roc.Average();
|
|
|
|
double sum = 0;
|
|
long count = 0;
|
|
|
|
foreach (var item in collection)
|
|
{
|
|
sum += item.ToDouble();
|
|
count++;
|
|
}
|
|
|
|
if (count == 0) return double.NaN.ToUnit<T>();
|
|
|
|
double avg = sum / count;
|
|
return avg.ToUnit<T>();
|
|
}
|
|
public static T Max<T>(this IEnumerable<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.MinValue.ToUnit<T>();
|
|
|
|
if (collection is T[] array) return array.Max();
|
|
if (collection is List<T> list) return list.Max();
|
|
if (collection is ICollection<T> col) return col.Max();
|
|
if (collection is IReadOnlyCollection<T> roc) return roc.Max();
|
|
|
|
double max = double.MinValue;
|
|
bool hasElements = false;
|
|
|
|
foreach (var item in collection)
|
|
{
|
|
double val = item.ToDouble();
|
|
if (val > max) max = val;
|
|
hasElements = true;
|
|
}
|
|
|
|
return hasElements ? max.ToUnit<T>() : double.MinValue.ToUnit<T>();
|
|
}
|
|
public static T Min<T>(this IEnumerable<T> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.MaxValue.ToUnit<T>();
|
|
|
|
if (collection is T[] array) return array.Min();
|
|
if (collection is List<T> list) return list.Min();
|
|
if (collection is ICollection<T> col) return col.Min();
|
|
if (collection is IReadOnlyCollection<T> roc) return roc.Min();
|
|
|
|
double min = double.MaxValue;
|
|
bool hasElements = false;
|
|
|
|
foreach (var item in collection)
|
|
{
|
|
double val = item.ToDouble();
|
|
if (val < min) min = val;
|
|
hasElements = true;
|
|
}
|
|
|
|
return hasElements ? min.ToUnit<T>() : double.MaxValue.ToUnit<T>();
|
|
}
|
|
|
|
|
|
|
|
// Sum Average Max Min (nullable) ==========================================
|
|
|
|
|
|
// === ReadOnlySpan ===
|
|
public static T Sum<T>(this ReadOnlySpan<T?> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty) return default;
|
|
|
|
// Берем массив из пула строго под размер исходного Span
|
|
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
|
int validCount = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < units.Length; i++)
|
|
{
|
|
T? unit = units[i];
|
|
if (unit.HasValue)
|
|
{
|
|
sharedArray[validCount++] = unit.Value.ToDouble();
|
|
}
|
|
}
|
|
|
|
if (validCount == 0) return default;
|
|
|
|
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
|
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
|
|
|
return validStructs.Sum(); // Наш SIMD метод
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Average<T>(this ReadOnlySpan<T?> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty)
|
|
{
|
|
double nan = double.NaN;
|
|
return Unsafe.As<double, T>(ref nan);
|
|
}
|
|
|
|
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
|
int validCount = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < units.Length; i++)
|
|
{
|
|
T? unit = units[i];
|
|
if (unit.HasValue)
|
|
{
|
|
sharedArray[validCount++] = unit.Value.ToDouble();
|
|
}
|
|
}
|
|
|
|
if (validCount == 0)
|
|
{
|
|
double nan = double.NaN;
|
|
return Unsafe.As<double, T>(ref nan);
|
|
}
|
|
|
|
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
|
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
|
|
|
return validStructs.Average(); // Наш SIMD метод
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Max<T>(this ReadOnlySpan<T?> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty)
|
|
{
|
|
double minVal = double.MinValue;
|
|
return Unsafe.As<double, T>(ref minVal);
|
|
}
|
|
|
|
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
|
int validCount = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < units.Length; i++)
|
|
{
|
|
T? unit = units[i];
|
|
if (unit.HasValue)
|
|
{
|
|
sharedArray[validCount++] = unit.Value.ToDouble();
|
|
}
|
|
}
|
|
|
|
if (validCount == 0)
|
|
{
|
|
double minVal = double.MinValue;
|
|
return Unsafe.As<double, T>(ref minVal);
|
|
}
|
|
|
|
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
|
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
|
|
|
return validStructs.Max(); // Наш SIMD метод
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Min<T>(this ReadOnlySpan<T?> units)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (units.IsEmpty)
|
|
{
|
|
double maxVal = double.MaxValue;
|
|
return Unsafe.As<double, T>(ref maxVal);
|
|
}
|
|
|
|
double[] sharedArray = ArrayPool<double>.Shared.Rent(units.Length);
|
|
int validCount = 0;
|
|
|
|
try
|
|
{
|
|
for (int i = 0; i < units.Length; i++)
|
|
{
|
|
T? unit = units[i];
|
|
if (unit.HasValue)
|
|
{
|
|
sharedArray[validCount++] = unit.Value.ToDouble();
|
|
}
|
|
}
|
|
|
|
if (validCount == 0)
|
|
{
|
|
double maxVal = double.MaxValue;
|
|
return Unsafe.As<double, T>(ref maxVal);
|
|
}
|
|
|
|
var validValues = new ReadOnlySpan<double>(sharedArray, 0, validCount);
|
|
ReadOnlySpan<T> validStructs = MemoryMarshal.Cast<double, T>(validValues);
|
|
|
|
return validStructs.Min(); // Наш SIMD метод
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<double>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
|
|
// === List<T> ===
|
|
public static T Sum<T>(this List<T?> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Sum();
|
|
}
|
|
public static T Average<T>(this List<T?> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Average();
|
|
}
|
|
public static T Max<T>(this List<T?> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Max();
|
|
}
|
|
public static T Min<T>(this List<T?> list)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
return CollectionsMarshal.AsSpan(list).Min();
|
|
}
|
|
|
|
// === ICollection<T> ===
|
|
public static T Sum<T>(this ICollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return default;
|
|
if (collection is T?[] array) return array.Sum();
|
|
if (collection is List<T?> list) return list.Sum();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Sum();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T?>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Average<T>(this ICollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Average();
|
|
if (collection is List<T?> list) return list.Average();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Average();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T?>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Max<T>(this ICollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Max();
|
|
if (collection is List<T?> list) return list.Max();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Max();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T?>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
public static T Min<T>(this ICollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Min();
|
|
if (collection is List<T?> list) return list.Min();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
try
|
|
{
|
|
collection.CopyTo(sharedArray, 0);
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Min();
|
|
}
|
|
finally
|
|
{
|
|
ArrayPool<T?>.Shared.Return(sharedArray);
|
|
}
|
|
}
|
|
|
|
// === IReadOnlyCollection<T> ===
|
|
public static T Sum<T>(this IReadOnlyCollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return default;
|
|
if (collection is T?[] array) return array.Sum();
|
|
if (collection is List<T?> list) return list.Sum();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Sum();
|
|
}
|
|
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Average<T>(this IReadOnlyCollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.NaN.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Average();
|
|
if (collection is List<T?> list) return list.Average();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Average();
|
|
}
|
|
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Max<T>(this IReadOnlyCollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MinValue.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Max();
|
|
if (collection is List<T?> list) return list.Max();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Max();
|
|
}
|
|
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
|
}
|
|
public static T Min<T>(this IReadOnlyCollection<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit<T>();
|
|
if (collection is T?[] array) return array.Min();
|
|
if (collection is List<T?> list) return list.Min();
|
|
|
|
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(collection.Count);
|
|
int index = 0;
|
|
try
|
|
{
|
|
foreach (var item in collection) sharedArray[index++] = item;
|
|
return new ReadOnlySpan<T?>(sharedArray, 0, collection.Count).Min();
|
|
}
|
|
finally { ArrayPool<T?>.Shared.Return(sharedArray); }
|
|
}
|
|
|
|
// === IEnumerable<T> ===
|
|
public static T Sum<T>(this IEnumerable<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return default;
|
|
|
|
// Быстрый SIMD-путь для готовых коллекций за 0 аллокаций
|
|
if (collection is T?[] array) return array.Sum();
|
|
if (collection is List<T?> list) return list.Sum();
|
|
if (collection is ICollection<T?> col) return col.Sum();
|
|
if (collection is IReadOnlyCollection<T?> roc) return roc.Sum();
|
|
|
|
// Медленный путь для ленивого yield return: вычисляем на лету
|
|
double sum = 0;
|
|
bool hasElements = false;
|
|
|
|
foreach (T? item in collection)
|
|
{
|
|
if (item.HasValue)
|
|
{
|
|
sum += item.Value.ToDouble();
|
|
hasElements = true;
|
|
}
|
|
}
|
|
|
|
return hasElements ? sum.ToUnit<T>() : default;
|
|
}
|
|
public static T Average<T>(this IEnumerable<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.NaN.ToUnit<T>();
|
|
|
|
if (collection is T?[] array) return array.Average();
|
|
if (collection is List<T?> list) return list.Average();
|
|
if (collection is ICollection<T?> col) return col.Average();
|
|
if (collection is IReadOnlyCollection<T?> roc) return roc.Average();
|
|
|
|
double sum = 0;
|
|
long count = 0;
|
|
|
|
foreach (T? item in collection)
|
|
{
|
|
if (item.HasValue)
|
|
{
|
|
sum += item.Value.ToDouble();
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) return double.NaN.ToUnit<T>();
|
|
|
|
double avg = sum / count;
|
|
return avg.ToUnit<T>();
|
|
}
|
|
public static T Max<T>(this IEnumerable<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.MinValue.ToUnit<T>();
|
|
|
|
if (collection is T?[] array) return array.Max();
|
|
if (collection is List<T?> list) return list.Max();
|
|
if (collection is ICollection<T?> col) return col.Max();
|
|
if (collection is IReadOnlyCollection<T?> roc) return roc.Max();
|
|
|
|
double max = double.MinValue;
|
|
bool hasElements = false;
|
|
|
|
foreach (T? item in collection)
|
|
{
|
|
if (item.HasValue)
|
|
{
|
|
double val = item.Value.ToDouble();
|
|
if (val > max) max = val;
|
|
hasElements = true;
|
|
}
|
|
}
|
|
|
|
return hasElements ? max.ToUnit<T>() : double.MinValue.ToUnit<T>();
|
|
}
|
|
public static T Min<T>(this IEnumerable<T?> collection)
|
|
where T : struct, IMensuraUnit, IEquatable<T>
|
|
{
|
|
if (collection == null) return double.MaxValue.ToUnit<T>();
|
|
|
|
if (collection is T?[] array) return array.Min();
|
|
if (collection is List<T?> list) return list.Min();
|
|
if (collection is ICollection<T?> col) return col.Min();
|
|
if (collection is IReadOnlyCollection<T?> roc) return roc.Min();
|
|
|
|
double min = double.MaxValue;
|
|
bool hasElements = false;
|
|
|
|
foreach (T? item in collection)
|
|
{
|
|
if (item.HasValue)
|
|
{
|
|
double val = item.Value.ToDouble();
|
|
if (val < min) min = val;
|
|
hasElements = true;
|
|
}
|
|
}
|
|
|
|
return hasElements ? min.ToUnit<T>() : double.MaxValue.ToUnit<T>();
|
|
}
|
|
} |