Files
QWERTYkez.Mensura/QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs
melekhin dae08feb4c 26.06.02
2026-06-02 12:28:46 +07:00

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>();
}
}