This commit is contained in:
melekhin
2026-06-02 12:28:46 +07:00
parent a6c7c7f2e6
commit dae08feb4c
17 changed files with 4356 additions and 6674 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,955 @@
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>();
}
}

View File

@@ -0,0 +1,65 @@
namespace QWERTYkez.Mensura.Extensions;
public static partial class CastExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double ToDouble<T>(this T unit)
where T : struct, IMensuraUnit, IEquatable<T>
{
return Unsafe.As<T, double>(ref unit);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double ToDouble<T>(this T? unit)
where T : struct, IMensuraUnit, IEquatable<T>
{
T actual = unit.GetValueOrDefault();
return Unsafe.As<T, double>(ref actual);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static T ToUnit<T>(this double val)
where T : struct, IMensuraUnit, IEquatable<T>
{
return Unsafe.As<double, T>(ref val);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void SetCountUnsafe<T>(this List<T> list, int count)
{
// Берем адрес управляемого объекта List в памяти
// Объект передается по ref-ссылке, преобразуется в указатель
ref var mimic = ref Unsafe.As<List<T>, ListLayoutMimic<T>>(ref list);
// Меняем приватный размер напрямую в памяти объекта!
mimic.Size = count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static List<T> WrapAsList<T>(this T[] array)
{
// Создаём пустой список с нулевой ёмкостью
var list = new List<T>(0);
// Получаем внутреннюю структуру списка
ref var mimic = ref Unsafe.As<List<T>, ListLayoutMimic<T>>(ref list);
// Подменяем массив и устанавливаем размер
mimic.Items = array;
mimic.Size = array.Length;
mimic.Version = 1; // любое ненулевое значение для консистенции
return list;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static List<R> WrapAsList<T, R>(this T[] array)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
return Unsafe.As<T[], R[]>(ref array).WrapAsList();
}
}

View File

@@ -0,0 +1,588 @@
using System.Buffers;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsDivideExtensions
{
// === ReadOnlySpan === SIMD
public static void Divide<T>(this ReadOnlySpan<T> units, double divisor, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
// Вместо деления в цикле, умножаем на обратное число (invDivisor)
double invDivisor = 1.0 / divisor;
var vectorizedInvDivisor = new Vector<double>(invDivisor);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
// Загрузка, быстрое умножение вместо деления, и сохранение
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
var multiplied = vector * vectorizedInvDivisor;
Unsafe.As<double, Vector<double>>(ref currentDst) = multiplied;
}
// Хвост
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * invDivisor;
}
}
public static void Divide<T>(this ReadOnlySpan<T?> units, double divisor, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
double invDivisor = 1.0 / divisor;
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (u0.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
d1 = u1.HasValue ? (u1.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
d2 = u2.HasValue ? (u2.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
d3 = u3.HasValue ? (u3.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (unit.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
}
}
//SIMD
public static void Divide<T>(this double dividend, ReadOnlySpan<T> units, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
var vectorizedDividend = new Vector<double>(dividend);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
// Векторное деление: константа делится на покомпонентные элементы массива
var divided = Vector.Divide(vectorizedDividend, vector);
Unsafe.As<double, Vector<double>>(ref currentDst) = divided;
}
// Хвост
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = dividend / Unsafe.Add(ref srcRef, i);
}
}
public static void Divide<T>(this double dividend, ReadOnlySpan<T?> units, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (dividend / u0.Value.ToDouble()).ToUnit<T>() : null;
d1 = u1.HasValue ? (dividend / u1.Value.ToDouble()).ToUnit<T>() : null;
d2 = u2.HasValue ? (dividend / u2.Value.ToDouble()).ToUnit<T>() : null;
d3 = u3.HasValue ? (dividend / u3.Value.ToDouble()).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (dividend / unit.Value.ToDouble()).ToUnit<T>() : null;
}
}
// === Array ===
public static T[] Divide<T>(this T[] units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Divide(units, divisor, result);
return result;
}
public static T?[] Divide<T>(this T?[] units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
// Выделяем чистую память (0 аллокаций логики, только сам массив)
var result = new T?[len];
double invDivisor = 1.0 / divisor;
for (int i = 0; i < len; i++)
{
// Читаем из исходного по значению (бесплатно для 8 байт)
T? item = units[i];
if (item.HasValue)
{
// Пишем напрямую в результат по ref-ссылке (всего 1 запись в память!)
ref var dst = ref result[i];
dst = (item.Value.ToDouble() * invDivisor).ToUnit<T>();
}
}
return result;
}
public static T[] Divide<T>(this double dividend, T[] units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Divide(dividend, units, result);
return result;
}
public static T?[] Divide<T>(this double dividend, T?[] units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T?[len];
for (int i = 0; i < len; i++)
{
T? item = units[i];
if (item.HasValue)
{
ref var dst = ref result[i];
dst = (dividend / item.Value.ToDouble()).ToUnit<T>();
}
}
return result;
}
// === List<T> ===
public static List<T> Divide<T>(this List<T> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Divide(CollectionsMarshal.AsSpan(units), divisor, resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Divide<T>(this List<T?> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
double invDivisor = 1.0 / divisor;
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() * invDivisor).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
public static List<T> Divide<T>(this double dividend, List<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Divide(dividend, CollectionsMarshal.AsSpan(units), resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Divide<T>(this double dividend, List<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (dividend / item.Value.ToDouble()).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
// === ICollection<T> ===
public static ICollection<T> Divide<T>(this ICollection<T> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Divide(divisor);
if (units is List<T> list) return list.Divide(divisor);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Divide(new ReadOnlySpan<T>(sharedArray, 0, count), divisor, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Divide<T>(this ICollection<T?> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Divide(divisor);
if (units is List<T?> list) return list.Divide(divisor);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
srcSpan.Divide(divisor, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
public static ICollection<T> Divide<T>(this double dividend, ICollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return dividend.Divide(array);
if (units is List<T> list) return dividend.Divide(list);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Divide(dividend, new ReadOnlySpan<T>(sharedArray, 0, count), finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Divide<T>(this double dividend, ICollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return dividend.Divide(array);
if (units is List<T?> list) return dividend.Divide(list);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
dividend.Divide(srcSpan, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IReadOnlyCollection<T> ===
public static IReadOnlyCollection<T> Divide<T>(this IReadOnlyCollection<T> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Divide(divisor);
if (units is List<T> list) return list.Divide(divisor);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Divide(sharedArray, divisor, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Divide<T>(this IReadOnlyCollection<T?> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Divide(divisor);
if (units is List<T?> list) return list.Divide(divisor);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
double invDivisor = 1.0 / divisor;
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() * invDivisor).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
public static IReadOnlyCollection<T> Divide<T>(this double dividend, IReadOnlyCollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Divide(dividend);
if (units is List<T> list) return list.Divide(dividend);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Divide(dividend, sharedArray, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Divide<T>(this double dividend, IReadOnlyCollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Divide(dividend);
if (units is List<T?> list) return list.Divide(dividend);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (dividend / item.Value.ToDouble()).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IEnumerable<T> + yeild ===
static IEnumerable<T> DivideIterator<T>(IEnumerable<T> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
double invDivisor = 1.0 / divisor;
foreach (var item in units)
yield return (item.ToDouble() * invDivisor).ToUnit<T>();
}
static IEnumerable<T?> DivideNullableIterator<T>(IEnumerable<T?> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
double invDivisor = 1.0 / divisor;
foreach (T? item in units)
yield return item.HasValue
? (item.Value.ToDouble() * invDivisor).ToUnit<T>() : null;
}
static IEnumerable<T> DivideIterator<T>(double dividend, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (var item in units)
yield return (dividend / item.ToDouble()).ToUnit<T>();
}
static IEnumerable<T?> DivideNullableIterator<T>(double dividend, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (T? item in units)
yield return item.HasValue
? (dividend / item.Value.ToDouble()).ToUnit<T>() : null;
}
// === IEnumerable<T> ===
public static IEnumerable<T> Divide<T>(this IEnumerable<T> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return array.Divide(divisor);
if (units is List<T> list) return list.Divide(divisor);
if (units is ICollection<T> col) return col.Divide(divisor);
if (units is IReadOnlyCollection<T> roc) return roc.Divide(divisor);
return DivideIterator(units, divisor);
}
public static IEnumerable<T?> Divide<T>(this IEnumerable<T?> units, double divisor)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return array.Divide(divisor);
if (units is List<T?> list) return list.Divide(divisor);
if (units is ICollection<T?> col) return col.Divide(divisor);
if (units is IReadOnlyCollection<T?> roc) return roc.Divide(divisor);
return DivideNullableIterator(units, divisor);
}
public static IEnumerable<T> Divide<T>(this double dividend, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return dividend.Divide(array);
if (units is List<T> list) return dividend.Divide(list);
if (units is ICollection<T> col) return dividend.Divide(col);
if (units is IReadOnlyCollection<T> roc) return dividend.Divide(roc);
return DivideIterator(dividend, units);
}
public static IEnumerable<T?> Divide<T>(this double dividend, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return dividend.Divide(array);
if (units is List<T?> list) return dividend.Divide(list);
if (units is ICollection<T?> col) return dividend.Divide(col);
if (units is IReadOnlyCollection<T?> roc) return dividend.Divide(roc);
return DivideNullableIterator(dividend, units);
}
}

View File

@@ -0,0 +1,575 @@
using System.Buffers;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsMinusExtensions
{
// === ReadOnlySpan === SIMD
public static void Minus<T>(this ReadOnlySpan<T> units, double subtrahend, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
var vectorizedSubtrahend = new Vector<double>(subtrahend);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
var subtracted = vector - vectorizedSubtrahend;
Unsafe.As<double, Vector<double>>(ref currentDst) = subtracted;
}
// Хвост
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) - subtrahend;
}
}
public static void Minus<T>(this ReadOnlySpan<T?> units, double subtrahend, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (u0.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
d1 = u1.HasValue ? (u1.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
d2 = u2.HasValue ? (u2.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
d3 = u3.HasValue ? (u3.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (unit.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
}
}
//SIMD
public static void Minus<T>(this double minuend, ReadOnlySpan<T> units, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
var vectorizedMinuend = new Vector<double>(minuend);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
// Векторное вычитание: константа уменьшается на покомпонентные элементы массива
var subtracted = vectorizedMinuend - vector;
Unsafe.As<double, Vector<double>>(ref currentDst) = subtracted;
}
// Хвост
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = minuend - Unsafe.Add(ref srcRef, i);
}
}
public static void Minus<T>(this double minuend, ReadOnlySpan<T?> units, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (minuend - u0.Value.ToDouble()).ToUnit<T>() : null;
d1 = u1.HasValue ? (minuend - u1.Value.ToDouble()).ToUnit<T>() : null;
d2 = u2.HasValue ? (minuend - u2.Value.ToDouble()).ToUnit<T>() : null;
d3 = u3.HasValue ? (minuend - u3.Value.ToDouble()).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (minuend - unit.Value.ToDouble()).ToUnit<T>() : null;
}
}
// === Array ===
public static T[] Minus<T>(this T[] units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Minus(units, subtrahend, result);
return result;
}
public static T?[] Minus<T>(this T?[] units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
// Выделяем чистую память (0 аллокаций логики, только сам массив)
var result = new T?[len];
for (int i = 0; i < len; i++)
{
// Читаем из исходного по значению (бесплатно для 8 байт)
T? item = units[i];
if (item.HasValue)
{
// Пишем напрямую в результат по ref-ссылке (всего 1 запись в память!)
ref var dst = ref result[i];
dst = (item.Value.ToDouble() - subtrahend).ToUnit<T>();
}
}
return result;
}
public static T[] Minus<T>(this double minuend, T[] units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Minus(minuend, units, result);
return result;
}
public static T?[] Minus<T>(this double minuend, T?[] units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T?[len];
for (int i = 0; i < len; i++)
{
T? item = units[i];
if (item.HasValue)
{
ref var dst = ref result[i];
dst = (minuend - item.Value.ToDouble()).ToUnit<T>();
}
}
return result;
}
// === List<T> ===
public static List<T> Minus<T>(this List<T> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Minus(CollectionsMarshal.AsSpan(units), subtrahend, resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Minus<T>(this List<T?> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() - subtrahend).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
public static List<T> Minus<T>(this double minuend, List<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Minus(minuend, CollectionsMarshal.AsSpan(units), resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Minus<T>(this double minuend, List<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (minuend - item.Value.ToDouble()).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
// === ICollection<T> ===
public static ICollection<T> Minus<T>(this ICollection<T> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Minus(subtrahend);
if (units is List<T> list) return list.Minus(subtrahend);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Minus(new ReadOnlySpan<T>(sharedArray, 0, count), subtrahend, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Minus<T>(this ICollection<T?> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Minus(subtrahend);
if (units is List<T?> list) return list.Minus(subtrahend);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
srcSpan.Minus(subtrahend, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
public static ICollection<T> Minus<T>(this double minuend, ICollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return minuend.Minus(array);
if (units is List<T> list) return minuend.Minus(list);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Minus(minuend, new ReadOnlySpan<T>(sharedArray, 0, count), finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Minus<T>(this double minuend, ICollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return minuend.Minus(array);
if (units is List<T?> list) return minuend.Minus(list);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
minuend.Minus(srcSpan, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IReadOnlyCollection<T> ===
public static IReadOnlyCollection<T> Minus<T>(this IReadOnlyCollection<T> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Multiply(subtrahend);
if (units is List<T> list) return list.Multiply(subtrahend);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Minus(sharedArray, subtrahend, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Minus<T>(this IReadOnlyCollection<T?> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Multiply(subtrahend);
if (units is List<T?> list) return list.Multiply(subtrahend);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() - subtrahend).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
public static IReadOnlyCollection<T> Minus<T>(this double minuend, IReadOnlyCollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Multiply(minuend);
if (units is List<T> list) return list.Multiply(minuend);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Minus(minuend, sharedArray, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Minus<T>(this double minuend, IReadOnlyCollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Multiply(minuend);
if (units is List<T?> list) return list.Multiply(minuend);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (minuend - item.Value.ToDouble()).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IEnumerable<T> + yeild ===
static IEnumerable<T> MinusIterator<T>(IEnumerable<T> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (var item in units)
yield return (item.ToDouble() - subtrahend).ToUnit<T>();
}
static IEnumerable<T?> MinusNullableIterator<T>(IEnumerable<T?> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (T? item in units)
yield return item.HasValue
? (item.Value.ToDouble() - subtrahend).ToUnit<T>() : null;
}
static IEnumerable<T> MinusIterator<T>(double minuend, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (var item in units)
yield return (minuend - item.ToDouble()).ToUnit<T>();
}
static IEnumerable<T?> MinusNullableIterator<T>(double minuend, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (T? item in units)
yield return item.HasValue
? (minuend - item.Value.ToDouble()).ToUnit<T>() : null;
}
// === IEnumerable<T> ===
public static IEnumerable<T> Minus<T>(this IEnumerable<T> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return array.Minus(subtrahend);
if (units is List<T> list) return list.Minus(subtrahend);
if (units is ICollection<T> col) return col.Minus(subtrahend);
if (units is IReadOnlyCollection<T> roc) return roc.Minus(subtrahend);
return MinusIterator(units, subtrahend);
}
public static IEnumerable<T?> Minus<T>(this IEnumerable<T?> units, double subtrahend)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return array.Minus(subtrahend);
if (units is List<T?> list) return list.Minus(subtrahend);
if (units is ICollection<T?> col) return col.Minus(subtrahend);
if (units is IReadOnlyCollection<T?> roc) return roc.Minus(subtrahend);
return MinusNullableIterator(units, subtrahend);
}
public static IEnumerable<T> Minus<T>(this double minuend, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return minuend.Minus(array);
if (units is List<T> list) return minuend.Minus(list);
if (units is ICollection<T> col) return minuend.Minus(col);
if (units is IReadOnlyCollection<T> roc) return minuend.Minus(roc);
return MinusIterator(minuend, units);
}
public static IEnumerable<T?> Minus<T>(this double minuend, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return minuend.Minus(array);
if (units is List<T?> list) return minuend.Minus(list);
if (units is ICollection<T?> col) return minuend.Minus(col);
if (units is IReadOnlyCollection<T?> roc) return minuend.Minus(roc);
return MinusNullableIterator(minuend, units);
}
}

View File

@@ -0,0 +1,346 @@
using System.Buffers;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsMultiplyExtensions
{
// === ReadOnlySpan === SIMD
public static void Multiply<T>(this ReadOnlySpan<T> units, double multiplicator, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
var vectorizedMultiplicator = new Vector<double>(multiplicator);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
var multiplied = vector * vectorizedMultiplicator;
Unsafe.As<double, Vector<double>>(ref currentDst) = multiplied;
}
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * multiplicator;
}
}
public static void Multiply<T>(this ReadOnlySpan<T?> units, double multiplicator, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (u0.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
d1 = u1.HasValue ? (u1.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
d2 = u2.HasValue ? (u2.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
d3 = u3.HasValue ? (u3.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (unit.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Multiply<T>(this double multiplicator, ReadOnlySpan<T> units, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator, destination);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Multiply<T>(this double multiplicator, ReadOnlySpan<T?> units, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator, destination);
// === Array ===
public static T[] Multiply<T>(this T[] units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Multiply(units, multiplicator, result);
return result;
}
public static T?[] Multiply<T>(this T?[] units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
// Выделяем чистую память (0 аллокаций логики, только сам массив)
var result = new T?[len];
for (int i = 0; i < len; i++)
{
// Читаем из исходного по значению (бесплатно для 8 байт)
T? item = units[i];
if (item.HasValue)
{
// Пишем напрямую в результат по ref-ссылке (всего 1 запись в память!)
ref var dst = ref result[i];
dst = (item.Value.ToDouble() * multiplicator).ToUnit<T>();
}
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Multiply<T>(this double multiplicator, T[] units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T?[] Multiply<T>(this double multiplicator, T?[] units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
// === List<T> ===
public static List<T> Multiply<T>(this List<T> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Multiply(CollectionsMarshal.AsSpan(units), multiplicator, resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Multiply<T>(this List<T?> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() * multiplicator).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T> Multiply<T>(this double multiplicator, List<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T?> Multiply<T>(this double multiplicator, List<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
// === ICollection<T> ===
public static ICollection<T> Multiply<T>(this ICollection<T> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Multiply(multiplicator);
if (units is List<T> list) return list.Multiply(multiplicator);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Multiply(new ReadOnlySpan<T>(sharedArray, 0, count), multiplicator, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Multiply<T>(this ICollection<T?> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Multiply(multiplicator);
if (units is List<T?> list) return list.Multiply(multiplicator);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
srcSpan.Multiply(multiplicator, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ICollection<T> Multiply<T>(this double multiplicator, ICollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ICollection<T?> Multiply<T>(this double multiplicator, ICollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
// === IReadOnlyCollection<T> ===
public static IReadOnlyCollection<T> Multiply<T>(this IReadOnlyCollection<T> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Multiply(multiplicator);
if (units is List<T> list) return list.Multiply(multiplicator);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Multiply(sharedArray, multiplicator, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Multiply<T>(this IReadOnlyCollection<T?> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Multiply(multiplicator);
if (units is List<T?> list) return list.Multiply(multiplicator);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() * multiplicator).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IReadOnlyCollection<T> Multiply<T>(this double multiplicator, IReadOnlyCollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IReadOnlyCollection<T?> Multiply<T>(this double multiplicator, IReadOnlyCollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Multiply(multiplicator);
// === IEnumerable<T> + yeild ===
static IEnumerable<T> MultiplyIterator<T>(IEnumerable<T> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (var item in units)
yield return (item.ToDouble() * multiplicator).ToUnit<T>();
}
static IEnumerable<T?> MultiplyNullableIterator<T>(IEnumerable<T?> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (T? item in units)
yield return item.HasValue
? (item.Value.ToDouble() * multiplicator).ToUnit<T>() : null;
}
// === IEnumerable<T> ===
public static IEnumerable<T> Multiply<T>(this IEnumerable<T> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return array.Multiply(multiplicator);
if (units is List<T> list) return list.Multiply(multiplicator);
if (units is ICollection<T> col) return col.Multiply(multiplicator);
if (units is IReadOnlyCollection<T> roc) return roc.Multiply(multiplicator);
return MultiplyIterator(units, multiplicator);
}
public static IEnumerable<T?> Multiply<T>(this IEnumerable<T?> units, double multiplicator)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return array.Multiply(multiplicator);
if (units is List<T?> list) return list.Multiply(multiplicator);
if (units is ICollection<T?> col) return col.Multiply(multiplicator);
if (units is IReadOnlyCollection<T?> roc) return roc.Multiply(multiplicator);
return MultiplyNullableIterator(units, multiplicator);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T> Multiply<T>(this double multiplicator, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => Multiply(units, multiplicator);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T?> Multiply<T>(this double multiplicator, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => Multiply(units, multiplicator);
}

View File

@@ -0,0 +1,346 @@
using System.Buffers;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsPlusExtensions
{
// === ReadOnlySpan === SIMD
public static void Plus<T>(this ReadOnlySpan<T> units, double summand, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<T, double>(destination);
var vectorizedSummand = new Vector<double>(summand);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
var vector = Unsafe.As<double, Vector<double>>(ref currentSrc);
var added = vector + vectorizedSummand;
Unsafe.As<double, Vector<double>>(ref currentDst) = added;
}
// Хвост
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) + summand;
}
}
public static void Plus<T>(this ReadOnlySpan<T?> units, double summand, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units.IsEmpty) return;
int len = units.Length;
// Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на ячейки назначения (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага)
d0 = u0.HasValue ? (u0.Value.ToDouble() + summand).ToUnit<T>() : null;
d1 = u1.HasValue ? (u1.Value.ToDouble() + summand).ToUnit<T>() : null;
d2 = u2.HasValue ? (u2.Value.ToDouble() + summand).ToUnit<T>() : null;
d3 = u3.HasValue ? (u3.Value.ToDouble() + summand).ToUnit<T>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? (unit.Value.ToDouble() + summand).ToUnit<T>() : null;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Plus<T>(this double summand, ReadOnlySpan<T> units, Span<T> destination)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand, destination);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Plus<T>(this double summand, ReadOnlySpan<T?> units, Span<T?> destination)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand, destination);
// === Array ===
public static T[] Plus<T>(this T[] units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new T[len];
Plus(units, summand, result);
return result;
}
public static T?[] Plus<T>(this T?[] units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
// Выделяем чистую память (0 аллокаций логики, только сам массив)
var result = new T?[len];
for (int i = 0; i < len; i++)
{
// Читаем из исходного по значению (бесплатно для 8 байт)
T? item = units[i];
if (item.HasValue)
{
// Пишем напрямую в результат по ref-ссылке (всего 1 запись в память!)
ref var dst = ref result[i];
dst = (item.Value.ToDouble() + summand).ToUnit<T>();
}
}
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T[] Plus<T>(this double summand, T[] units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T?[] Plus<T>(this double summand, T?[] units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
// === List<T> ===
public static List<T> Plus<T>(this List<T> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new T[len];
Plus(CollectionsMarshal.AsSpan(units), summand, resultArray);
return resultArray.WrapAsList();
}
public static List<T?> Plus<T>(this List<T?> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new T?[count];
ReadOnlySpan<T?> srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < count; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() + summand).ToUnit<T>();
}
}
return resultArray.WrapAsList();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T> Plus<T>(this double summand, List<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<T?> Plus<T>(this double summand, List<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
// === ICollection<T> ===
public static ICollection<T> Plus<T>(this ICollection<T> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Plus(summand);
if (units is List<T> list) return list.Plus(summand);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
units.CopyTo(sharedArray, 0);
Plus(new ReadOnlySpan<T>(sharedArray, 0, count), summand, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<T?> Plus<T>(this ICollection<T?> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Plus(summand);
if (units is List<T?> list) return list.Plus(summand);
var resultArray = new T?[count];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
try
{
units.CopyTo(sharedNullableArray, 0);
var srcSpan = new ReadOnlySpan<T?>(sharedNullableArray, 0, count);
srcSpan.Plus(summand, resultArray);
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ICollection<T> Plus<T>(this double summand, ICollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ICollection<T?> Plus<T>(this double summand, ICollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
// === IReadOnlyCollection<T> ===
public static IReadOnlyCollection<T> Plus<T>(this IReadOnlyCollection<T> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Plus(summand);
if (units is List<T> list) return list.Plus(summand);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new T[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Plus(sharedArray, summand, finalResult.AsSpan());
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static IReadOnlyCollection<T?> Plus<T>(this IReadOnlyCollection<T?> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Plus(summand);
if (units is List<T?> list) return list.Plus(summand);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(count);
var resultArray = new T?[count];
try
{
int index = 0;
foreach (var item in units) sharedNullableArray[index++] = item;
for (int i = 0; i < count; i++)
{
T? item = sharedNullableArray[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = (item.Value.ToDouble() + summand).ToUnit<T>();
}
}
return resultArray;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IReadOnlyCollection<T> Plus<T>(this double summand, IReadOnlyCollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IReadOnlyCollection<T?> Plus<T>(this double summand, IReadOnlyCollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => units.Plus(summand);
// === IEnumerable<T> + yeild ===
static IEnumerable<T> PlusIterator<T>(IEnumerable<T> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (var item in units)
yield return (item.ToDouble() + summand).ToUnit<T>();
}
static IEnumerable<T?> PlusNullableIterator<T>(IEnumerable<T?> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
foreach (T? item in units)
yield return item.HasValue
? (item.Value.ToDouble() + summand).ToUnit<T>() : null;
}
// === IEnumerable<T> ===
public static IEnumerable<T> Plus<T>(this IEnumerable<T> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T[] array) return array.Plus(summand);
if (units is List<T> list) return list.Plus(summand);
if (units is ICollection<T> col) return col.Plus(summand);
if (units is IReadOnlyCollection<T> roc) return roc.Plus(summand);
return PlusIterator(units, summand);
}
public static IEnumerable<T?> Plus<T>(this IEnumerable<T?> units, double summand)
where T : struct, IMensuraUnit, IEquatable<T>
{
if (units is null) return null!;
if (units is T?[] array) return array.Plus(summand);
if (units is List<T?> list) return list.Plus(summand);
if (units is ICollection<T?> col) return col.Plus(summand);
if (units is IReadOnlyCollection<T?> roc) return roc.Plus(summand);
return PlusNullableIterator(units, summand);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T> Plus<T>(this double summand, IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T> => Plus(units, summand);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static IEnumerable<T?> Plus<T>(this double summand, IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T> => Plus(units, summand);
}

View File

@@ -0,0 +1,710 @@
using System.Buffers;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsPowExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static double QuickPow(this double base_Value, int exp)
{
switch (exp)
{
case 0: return 1.0;
case 1: return base_Value;
case 2: return base_Value * base_Value;
case 3: return base_Value * base_Value * base_Value;
case 4: { double x2 = base_Value * base_Value; return x2 * x2; }
case -1: return 1.0 / base_Value;
case -2: return 1.0 / (base_Value * base_Value);
default: return Math.Pow(base_Value, exp);
}
}
// === ЦЕЛАЯ СТЕПЕНЬ ==========================================
// === ReadOnlySpan === SIMD
internal static void Pow<T, R>(this ReadOnlySpan<T> units, int power, Span<R> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
int len = units.Length;
if (len == 0) return;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<R, double>(destination);
int vectorSize = Vector<double>.Count;
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
// Быстрая обработка граничных случаев для исключения лишних циклов
if (power == 0)
{
// Любое число в степени 0 равно 1
dstDouble.Fill(1.0);
return;
}
if (power == 1)
{
// В степени 1 — это просто копирование памяти
srcDouble.CopyTo(dstDouble);
return;
}
int simdEnd = len & ~(vectorSize - 1);
for (; i < simdEnd; i += vectorSize)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
// Загружаем вектор за 1 такт процессора
var @base = Unsafe.As<double, Vector<double>>(ref currentSrc);
// Векторный аналог быстрого (бинарного) возведения в степень
var result = Vector<double>.One;
var currentPower = Math.Abs(power);
while (currentPower > 0)
{
if ((currentPower & 1) == 1)
{
result *= @base;
}
@base *= @base;
currentPower >>= 1;
}
// Если степень отрицательная, делим единицу на полученный результат
if (power < 0)
{
result = Vector.Divide(Vector<double>.One, result);
}
// Сохраняем итоговый вектор
Unsafe.As<double, Vector<double>>(ref currentDst) = result;
}
// Хвост массива (или обычный расчет, если SIMD не поддерживается)
var absPower = Math.Abs(power);
for (; i < len; i++)
{
double @base = Unsafe.Add(ref srcRef, i);
double result = 1.0;
int currentPower = absPower;
while (currentPower > 0)
{
if ((currentPower & 1) == 1) result *= @base;
@base *= @base;
currentPower >>= 1;
}
Unsafe.Add(ref dstRef, i) = power < 0 ? 1.0 / result : result;
}
}
internal static void Pow<T, R>(this ReadOnlySpan<T?> units, int power, Span<R?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
int len = units.Length;
if (len == 0) return;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
// Получаем прямые ref-ссылки на начало буферов за 0 тактов процессора
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Параллельный конвейер по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на целевые ячейки типа R? (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Выполняем быструю бинарную математику и трансформируем тип из T в R по месту
d0 = u0.HasValue ? u0.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
d1 = u1.HasValue ? u1.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
d2 = u2.HasValue ? u2.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
d3 = u3.HasValue ? u3.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 шчку)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? unit.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
}
}
// === Array ===
internal static R[] Pow<T, R>(this T[] units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new R[len];
Pow(units, power, result);
return result;
}
internal static R?[] Pow<T, R>(this T?[] units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var result = new R?[len];
Pow(units, power, result);
return result;
}
// === List<Length> ===
internal static List<R> Pow<T, R>(this List<T> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new R[count];
Pow(CollectionsMarshal.AsSpan(units), power, resultArray);
return resultArray.WrapAsList();
}
internal static List<R?> Pow<T, R>(this List<T?> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
var resultArray = new R?[count];
Pow(CollectionsMarshal.AsSpan(units), power, resultArray);
return resultArray.WrapAsList();
}
// === ICollection<Length> ===
internal static ICollection<R> Pow<T, R>(this ICollection<T> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
ICollection<R> result = [];
foreach (var item in units)
result.Add(item.ToDouble().QuickPow(power).ToUnit<R>());
return result;
}
internal static ICollection<R?> Pow<T, R>(this ICollection<T?> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
ICollection<R?> result = [];
foreach (var item in units)
result.Add((item.HasValue ? item.Value.ToDouble().QuickPow(power) : 0d).ToUnit<R>());
return result;
}
// === IReadOnlyCollection<Length> ===
internal static IReadOnlyCollection<R> Pow<T, R>(this IReadOnlyCollection<T> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
T[] sharedArray = ArrayPool<T>.Shared.Rent(count);
var finalResult = new R[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Pow(sharedArray, power, finalResult);
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
internal static IReadOnlyCollection<R?> Pow<T, R>(this IReadOnlyCollection<T?> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int count = units.Count;
if (count == 0) return [];
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
T?[] sharedArray = ArrayPool<T?>.Shared.Rent(count);
var finalResult = new R?[count];
try
{
int index = 0;
foreach (var item in units) sharedArray[index++] = item;
Pow(sharedArray, power, finalResult);
return finalResult;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedArray);
}
}
// === IEnumerable<T, R> + yeild ===
static IEnumerable<R> PowIterator<T, R>(IEnumerable<T> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return (item.ToDouble().QuickPow(power)).ToUnit<R>();
}
static IEnumerable<R?> PowNullableIterator<T, R>(IEnumerable<T?> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return item.HasValue
? item.Value.ToDouble().QuickPow(power).ToUnit<R>() : null;
}
// === IEnumerable<Length> ===
internal static IEnumerable<R> Pow<T, R>(this IEnumerable<T> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
if (units is ICollection<T> col) return col.Pow<T, R>(power);
if (units is IReadOnlyCollection<T> roc) return roc.Pow<T, R>(power);
else return PowIterator<T, R>(units, power);
}
internal static IEnumerable<R?> Pow<T, R>(this IEnumerable<T?> units, int power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
if (units is ICollection<T?> col) return col.Pow<T, R>(power);
if (units is IReadOnlyCollection<T?> roc) return roc.Pow<T, R>(power);
else return PowNullableIterator<T, R>(units, power);
}
// === ДРОБНАЯ СТЕПЕНЬ ==========================================
// === ReadOnlySpan ===
internal static void Pow<T, R>(this ReadOnlySpan<T> units, double power, Span<R> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
int len = units.Length;
if (len == 0) return;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<R, double>(destination);
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
// 1. Оптимизация тривиальных случаев
if (power == 1.0)
{
srcDouble.CopyTo(dstDouble);
return;
}
if (power == 0.0)
{
dstDouble.Fill(1.0);
return;
}
// 2. Оптимизация для квадратного корня (степень 0.5) через аппаратный AVX
if (power == 0.5)
{
if (Avx.IsSupported && len >= 4)
{
int simdEnd = len & ~3;
for (; i < simdEnd; i += 4)
{
// Совместимый с .NET 6 способ чтения вектора из ref double
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
var v = Unsafe.As<double, Vector256<double>>(ref currentSrc);
// Аппаратный корень через инструкцию vsqrtpd
var sqrtV = Avx.Sqrt(v);
// Совместимый с .NET 6 способ записи вектора в ref double
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
Unsafe.As<double, Vector256<double>>(ref currentDst) = sqrtV;
}
}
// Хвост для корня
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i));
}
return;
}
// 3. ОБЩИЙ СЛУЧАЙ ДЛЯ СЛОЖНЫХ СТЕПЕНЕЙ
// Из-за ограничений .NET 6 здесь мы разворачиваем цикл вручную на 4 элемента,
// но делаем это через ref-ссылки без использования fixed-указателей
if (len >= 4)
{
int simdEnd = len & ~3;
for (; i < simdEnd; i += 4)
{
ref double sRef = ref Unsafe.Add(ref srcRef, i);
ref double dRef = ref Unsafe.Add(ref dstRef, i);
// JIT попытается это автовекторизовать, но даже без нее
// этот код работает быстрее fixed за счет отсутствия пиннинга памяти GC
Unsafe.Add(ref dRef, 0) = Math.Pow(Unsafe.Add(ref sRef, 0), power);
Unsafe.Add(ref dRef, 1) = Math.Pow(Unsafe.Add(ref sRef, 1), power);
Unsafe.Add(ref dRef, 2) = Math.Pow(Unsafe.Add(ref sRef, 2), power);
Unsafe.Add(ref dRef, 3) = Math.Pow(Unsafe.Add(ref sRef, 3), power);
}
}
// Хвост массива
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Math.Pow(Unsafe.Add(ref srcRef, i), power);
}
}
internal static void Pow<T, R>(this ReadOnlySpan<T?> units, double power, Span<R?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
int len = units.Length;
if (len == 0) return;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
for (int i = 0; i < len; i++)
{
T? item = units[i];
if (item.HasValue)
{
// Прямая ref-запись в переданный снаружи destination
ref var dst = ref destination[i];
dst = Math.Pow(item.Value.ToDouble(), power).ToUnit<R>();
}
}
}
// === Array ===
internal static R[] Pow<T, R>(this T[] units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Length;
if (len == 0) return [];
var finalResult = new R[len];
if (power != 0.5)
{
for (int i = 0; i < len; i++)
{
ref var dst = ref finalResult[i];
dst = Math.Pow(units[i].ToDouble(), power).ToUnit<R>();
}
}
else MemoryMarshal.Cast<T, R>(units).Sqrt(finalResult);
return finalResult;
}
internal static R?[] Pow<T, R>(this T?[] units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units.Length == 0) return [];
var result = (R?[])units.Clone();
int len = result.Length;
for (int i = 0; i < len; i++)
{
ref var item = ref result[i];
if (item.HasValue)
{
item = Math.Pow(item.Value.ToDouble(), power).ToUnit<R>();
}
}
return result;
}
// === List<Length> ===
internal static List<R> Pow<T, R>(this List<T> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new R[len];
var srcSpan = CollectionsMarshal.AsSpan(units);
if (power == 0.5)
{
ReadOnlySpan<R> srcAsR = MemoryMarshal.Cast<T, R>(srcSpan);
srcAsR.Sqrt(resultArray);
}
else
{
Span<R> dstSpan = resultArray.AsSpan();
for (int i = 0; i < len; i++)
{
ref var dst = ref dstSpan[i];
dst = Math.Pow(srcSpan[i].ToDouble(), power).ToUnit<R>();
}
}
return resultArray.WrapAsList();
}
internal static List<R?> Pow<T, R>(this List<T?> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new R?[len];
var srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < len; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = Math.Pow(item.Value.ToDouble(), power).ToUnit<R>();
}
}
return resultArray.WrapAsList();
}
// === ICollection<Length> ===
internal static ICollection<R> Pow<T, R>(this ICollection<T> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
T[] sharedArray = ArrayPool<T>.Shared.Rent(len);
var finalResult = new R[len];
try
{
units.CopyTo(sharedArray, 0);
if (power == 0.5)
{
MemoryMarshal.Cast<T, R>(sharedArray).Sqrt(finalResult);
}
else
{
for (int i = 0; i < len; i++)
finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit<R>();
}
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
internal static ICollection<R?> Pow<T, R>(this ICollection<T?> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(len);
var finalResult = new R?[len];
try
{
units.CopyTo(sharedNullableArray, 0);
for (int i = 0; i < len; i++)
{
T? item = sharedNullableArray[i];
finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit<R>() : default;
}
return finalResult;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IReadOnlyCollection<Length> ===
internal static IReadOnlyCollection<R> Pow<T, R>(this IReadOnlyCollection<T> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
var finalResult = new R[len];
var sharedArray = ArrayPool<T>.Shared.Rent(len);
try
{
if (units is ICollection<T> collection)
{
collection.CopyTo(sharedArray, 0);
}
else
{
int idx = 0;
foreach (var item in units) sharedArray[idx++] = item;
}
if (power != 0.5)
{
for (int i = 0; i < len; i++)
finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit<R>();
}
else MemoryMarshal.Cast<T, R>(sharedArray).Sqrt(finalResult);
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
internal static IReadOnlyCollection<R?> Pow<T, R>(this IReadOnlyCollection<T?> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
var finalResult = new R?[len];
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(len);
try
{
if (units is ICollection<T?> collection)
{
collection.CopyTo(sharedNullableArray, 0);
}
else
{
int idx = 0;
foreach (T? item in units) sharedNullableArray[idx++] = item;
}
for (int i = 0; i < len; i++)
{
T? item = sharedNullableArray[i];
finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit<R>() : default;
}
return finalResult;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IEnumerable<Length> + yield ===
internal static IEnumerable<R> PowIterator<T, R>(this IEnumerable<T> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return Math.Pow(item.ToDouble(), power).ToUnit<R>();
}
internal static IEnumerable<R?> PowNullableIterator<T, R>(this IEnumerable<T?> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return item.HasValue
? Math.Pow(item.Value.ToDouble(), power).ToUnit<R>() : null;
}
// === IEnumerable<Length> ===
internal static IEnumerable<R> Pow<T, R>(this IEnumerable<T> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T[] array) return array.Pow<T, R>(power);
if (units is List<T> list) return list.Pow<T, R>(power);
if (units is ICollection<T> col) return col.Pow<T, R>(power);
if (units is IReadOnlyCollection<T> roc) return roc.Pow<T, R>(power);
else return PowIterator<T, R>(units, power);
}
internal static IEnumerable<R?> Pow<T, R>(this IEnumerable<T?> units, double power)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T?[] array) return array.Pow<T, R>(power);
if (units is List<T?> list) return list.Pow<T, R>(power);
if (units is ICollection<T?> col) return col.Pow<T, R>(power);
if (units is IReadOnlyCollection<T?> roc) return roc.Pow<T, R>(power);
else return PowNullableIterator<T, R>(units, power);
}
}

View File

@@ -0,0 +1,269 @@
using System.Buffers;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace QWERTYkez.Mensura.Extensions;
public static partial class CollectionsSqrtExtensions
{
// === ReadOnlySpan === SIMD
internal static void Sqrt<T, R>(this ReadOnlySpan<T> units, Span<R> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(units);
Span<double> dstDouble = MemoryMarshal.Cast<R, double>(destination);
int i = 0;
ref double srcRef = ref MemoryMarshal.GetReference(srcDouble);
ref double dstRef = ref MemoryMarshal.GetReference(dstDouble);
// 1. ПУТЬ AVX (x64 Процессоры Intel/AMD) — обрабатываем по 4 элемента double
if (Avx.IsSupported && len >= 4)
{
int simdEnd = len & ~3; // Вычисляем границу по маске (быстрее, чем len - len % 4)
for (; i < simdEnd; i += 4)
{
ref double currentSrc = ref Unsafe.Add(ref srcRef, i);
ref double currentDst = ref Unsafe.Add(ref dstRef, i);
// Загружаем 4 элемента double в AVX регистр за 1 такт
var v = Unsafe.As<double, Vector256<double>>(ref currentSrc);
// Аппаратный корень на уровне CPU
var sqrtV = Avx.Sqrt(v);
// Выгружаем результат обратно в память назначения за 1 такт
Unsafe.As<double, Vector256<double>>(ref currentDst) = sqrtV;
}
}
// 2. ПУТЬ VECTOR (ARM64 / Apple Silicon / Старые CPU без AVX)
else if (Vector.IsHardwareAccelerated && len >= Vector<double>.Count)
{
int vCount = Vector<double>.Count;
int simdEnd = len & ~(vCount - 1);
for (; i < simdEnd; i += vCount)
{
// Используем Span для кроссплатформенного создания вектора
var v = new Vector<double>(srcDouble.Slice(i, vCount));
// Кроссплатформенный аппаратный корень (на ARM превратится в NEON инструкцию)
var sqrtV = Vector.SquareRoot(v);
// Копируем напрямую в целевой Span
sqrtV.CopyTo(dstDouble.Slice(i, vCount));
}
}
// 3. Хвост массива (или обычный расчет, если SIMD на процессоре недоступен)
for (; i < len; i++)
{
Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i));
}
}
public static void Sqrt<T, R>(this ReadOnlySpan<T?> units, Span<R?> destination)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units.IsEmpty) return;
int len = units.Length;
if (len > destination.Length)
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
// Получаем прямые ref-ссылки на начало буферов за 0 тактов процессора
ref var srcRef = ref MemoryMarshal.GetReference(units);
ref var dstRef = ref MemoryMarshal.GetReference(destination);
int i = 0;
int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4)
// 1. ОСНОВНОЙ ЦИКЛ: Конвейерная обработка по 4 элемента за итерацию
for (; i < unrollEnd; i += 4)
{
T? u0 = Unsafe.Add(ref srcRef, i);
T? u1 = Unsafe.Add(ref srcRef, i + 1);
T? u2 = Unsafe.Add(ref srcRef, i + 2);
T? u3 = Unsafe.Add(ref srcRef, i + 3);
// Получаем ref-ссылки на целевые ячейки типа R? (zero-cost адресация)
ref var d0 = ref Unsafe.Add(ref dstRef, i);
ref var d1 = ref Unsafe.Add(ref dstRef, i + 1);
ref var d2 = ref Unsafe.Add(ref dstRef, i + 2);
ref var d3 = ref Unsafe.Add(ref dstRef, i + 3);
// Считаем нативный корень прямо в регистрах и трансформируем тип из T в R по месту
d0 = u0.HasValue ? Math.Sqrt(u0.Value.ToDouble()).ToUnit<R>() : null;
d1 = u1.HasValue ? Math.Sqrt(u1.Value.ToDouble()).ToUnit<R>() : null;
d2 = u2.HasValue ? Math.Sqrt(u2.Value.ToDouble()).ToUnit<R>() : null;
d3 = u3.HasValue ? Math.Sqrt(u3.Value.ToDouble()).ToUnit<R>() : null;
}
// 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук)
for (; i < len; i++)
{
T? unit = Unsafe.Add(ref srcRef, i);
ref var dst = ref Unsafe.Add(ref dstRef, i);
dst = unit.HasValue ? Math.Sqrt(unit.Value.ToDouble()).ToUnit<R>() : null;
}
}
// === Array ===
public static R[] Sqrt<T, R>(this T[] units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units.Length == 0) return [];
var result = new R[units.Length];
Sqrt(units, result);
return result;
}
public static R?[] Sqrt<T, R>(this T?[] units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units.Length == 0) return [];
var result = new R?[units.Length];
Sqrt(units, result);
return result;
}
// === List<Length> ===
public static List<R> Sqrt<T, R>(this List<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new R[len];
Sqrt(CollectionsMarshal.AsSpan(units), resultArray);
return resultArray.WrapAsList();
}
internal static List<R?> Sqrt<T, R>(this List<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
var resultArray = new R?[len];
var srcSpan = CollectionsMarshal.AsSpan(units);
for (int i = 0; i < len; i++)
{
T? item = srcSpan[i];
if (item.HasValue)
{
ref var dst = ref resultArray[i];
dst = Math.Sqrt(item.Value.ToDouble()).ToUnit<R>();
}
}
return resultArray.WrapAsList();
}
// === ICollection<Length> ===
public static ICollection<R> Sqrt<T, R>(this ICollection<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T[] array) return array.Sqrt<T, R>();
if (units is List<T> list) return list.Sqrt<T, R>();
T[] sharedArray = ArrayPool<T>.Shared.Rent(len);
var finalResult = new R[len];
try
{
units.CopyTo(sharedArray, 0);
MemoryMarshal.Cast<T, R>(sharedArray).Sqrt(finalResult);
return finalResult;
}
finally
{
ArrayPool<T>.Shared.Return(sharedArray);
}
}
public static ICollection<R?> Sqrt<T, R>(this ICollection<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
int len = units.Count;
if (len == 0) return [];
if (units is T?[] array) return array.Sqrt<T, R>();
if (units is List<T?> list) return list.Sqrt<T, R>();
T?[] sharedNullableArray = ArrayPool<T?>.Shared.Rent(len);
var finalResult = new R?[len];
try
{
units.CopyTo(sharedNullableArray, 0);
for (int i = 0; i < len; i++)
{
T? item = sharedNullableArray[i];
finalResult[i] = item.HasValue ? Math.Sqrt(item.Value.ToDouble()).ToUnit<R>() : default;
}
return finalResult;
}
finally
{
ArrayPool<T?>.Shared.Return(sharedNullableArray);
}
}
// === IEnumerable<Length> + yield ===
public static IEnumerable<R> SqrtIterator<T, R>(this IEnumerable<T> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return Math.Sqrt(item.ToDouble()).ToUnit<R>();
}
public static IEnumerable<R?> SqrtNullableIterator<T, R>(this IEnumerable<T?> units)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
foreach (var item in units)
yield return item.HasValue
? Math.Sqrt(item.Value.ToDouble()).ToUnit<R>() : null;
}
// === IEnumerable<Length> ===
internal static IEnumerable<R> Sqrt<T, R>(this IEnumerable<T> units, double Sqrter)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T[] array) return array.Sqrt<T, R>(Sqrter);
if (units is List<T> list) return list.Sqrt<T, R>(Sqrter);
if (units is ICollection<T> col) return col.Sqrt<T, R>(Sqrter);
if (units is IReadOnlyCollection<T> roc) return roc.Sqrt<T, R>(Sqrter);
else return SqrtIterator<T, R>(units);
}
internal static IEnumerable<R?> Sqrt<T, R>(this IEnumerable<T?> units, double Sqrter)
where T : struct, IMensuraUnit, IEquatable<T>
where R : struct, IMensuraUnit, IEquatable<R>
{
if (units is null) return null!;
if (units is T?[] array) return array.Sqrt<T, R>(Sqrter);
if (units is List<T?> list) return list.Sqrt<T, R>(Sqrter);
if (units is ICollection<T?> col) return col.Sqrt<T, R>(Sqrter);
if (units is IReadOnlyCollection<T?> roc) return roc.Sqrt<T, R>(Sqrter);
else return SqrtNullableIterator<T, R>(units);
}
}

View File

@@ -0,0 +1,40 @@
using System.Globalization;
namespace QWERTYkez.Mensura.Extensions;
internal static partial class ToDoubleExtensions
{
internal static double ToDouble(this sbyte number) => Convert.ToDouble(number);
internal static double ToDouble(this short number) => Convert.ToDouble(number);
internal static double ToDouble(this int number) => Convert.ToDouble(number);
internal static double ToDouble(this long number) => Convert.ToDouble(number);
internal static double ToDouble(this byte number) => Convert.ToDouble(number);
internal static double ToDouble(this ushort number) => Convert.ToDouble(number);
internal static double ToDouble(this uint number) => Convert.ToDouble(number);
internal static double ToDouble(this ulong number) => Convert.ToDouble(number);
internal static double ToDouble(this nint number) => Convert.ToDouble(number);
internal static double ToDouble(this nuint number) => Convert.ToDouble(number);
internal static double ToDouble(this float number) => Convert.ToDouble(number);
internal static double ToDouble(this decimal number) => Convert.ToDouble(number);
internal static double ToDouble(this sbyte? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this short? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this int? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this long? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this byte? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this ushort? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this uint? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this ulong? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this nint? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this nuint? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this float? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this decimal? number) => number is null ? 0d : Convert.ToDouble(number);
#if NET7_0_OR_GREATER
internal static double ToDouble(this Int128 number) => Convert.ToDouble(number);
internal static double ToDouble(this UInt128 number) => Convert.ToDouble(number);
internal static double ToDouble(this Int128? number) => number is null ? 0d : Convert.ToDouble(number);
internal static double ToDouble(this UInt128? number) => number is null ? 0d : Convert.ToDouble(number);
#endif
}

View File

@@ -0,0 +1,338 @@
using System.Buffers;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace QWERTYkez.Mensura;
public static partial class Extensions2
{
// ==========================================
// CORE
// ==========================================
// ==========================================
// === MULTIPLY ===
// ==========================================
// ==========================================
// === DIVIDE ===
// ==========================================
// ==========================================
// === PLUS ===
// ==========================================
// ==========================================
// === MINUS ===
// ==========================================
//internal static U Protect<U>(this U? metric) where U : class, IMetric<U>, new() => metric ?? new();
//internal static C Protect<C, U>(this C? collection)
// where C : IMetricCollection<U>, new() where U : class, IMetric<U>, new() => collection ?? new();
//public static C MetricSelect<C, U>(this C? collection, Func<U, U>? selector)
// where C : IMetricCollection<U>, new() where U : class, IMetric<U>, new()
//{
// var source = (collection ??= new());
// var nColl = (C)source.CreateByInstanceU(source.Count);
// if (selector is not null)
// {
// for (int i = 0; i < nColl.Count; i++)
// nColl[i] = selector(source[i]);
// return nColl;
// }
// return new();
//}
//public static IEnumerable<double> MetricSelect<U>(this IMetricCollection<U> collection, Func<U, double> selector)
// where U : class, IMetric<U>, new()
//{
// if (collection is not null)
// {
// if (selector is not null)
// return collection.Select(selector);
// return collection.Select(u => double.NaN);
// }
// else return [];
//}
//public static IMetricCollection<Uz> MetricSelect<Ux, Uz>(this IMetricCollection<Ux>? collection, Func<Ux, Uz>? selector)
// where Ux : class, IMetric<Ux>, new() where Uz : class, IMetric<Uz>, new()
//{
// if (collection is not null && selector is not null)
// {
// var destCollection = collection.CreateByInstance<Uz>(collection.Count);
// for (int i = 0; i < collection.Count; i++)
// destCollection[i] = selector(collection[i]);
// return destCollection;
// }
// return null!;
//}
//public static MetricCollection<Uz> MetricSelect<Ux, Uz>(this MetricCollection<Ux>? collection, Func<Ux, Uz>? selector)
// where Ux : class, IMetric<Ux>, new() where Uz : class, IMetric<Uz>, new()
//{
// if (collection is not null && selector is not null)
// {
// var destCollection = collection.CreateByInstance<Uz>(collection.Count());
// for (int i = 0; i < collection.Count(); i++)
// destCollection[i] = selector(collection[i]);
// return destCollection;
// }
// return null!;
//}
//public static MetricArray<Uz> MetricSelect<Ux, Uz>(this MetricArray<Ux>? collection, Func<Ux, Uz>? selector)
// where Ux : class, IMetric<Ux>, new() where Uz : class, IMetric<Uz>, new()
//{
// var coll = collection?.ToArray();
// if (coll is not null && selector is not null)
// {
// var destCollection = new MetricArray<Uz>(coll.Length);
// for (int i = 0; i < coll.Length; i++)
// destCollection[i] = selector(coll[i]);
// return destCollection;
// }
// return null!;
//}
//public static MetricList<Uz> MetricSelect<Ux, Uz>(this MetricList<Ux>? collection, Func<Ux, Uz>? selector)
// where Ux : class, IMetric<Ux>, new() where Uz : class, IMetric<Uz>, new()
//{
// if (collection is not null && selector is not null)
// {
// var destCollection = new MetricList<Uz>(collection.Count);
// for (int i = 0; i < collection.Count; i++)
// destCollection[i] = selector(collection[i]);
// return destCollection;
// }
// return null!;
//}
//public static MetricObservableCollection<Uz> MetricSelect<Ux, Uz>(this MetricObservableCollection<Ux>? collection, Func<Ux, Uz>? selector)
// where Ux : class, IMetric<Ux>, new() where Uz : class, IMetric<Uz>, new()
//{
// if (collection is not null && selector is not null)
// {
// var destCollection = new MetricObservableCollection<Uz>(collection.Count);
// for (int i = 0; i < collection.Count; i++)
// destCollection[i] = selector(collection[i]);
// return destCollection;
// }
// return null!;
//}
//public static double[] MetricSelect<U>(this MetricArray<U> collection, Func<U, double> selector)
// where U : class, IMetric<U>, new()
//{
// var coll = collection ?? [];
// var arr = new double[coll.Length];
// if (selector is not null)
// for (int i = 0; i < arr.Length; i++)
// arr[i] = selector(coll[i]);
// return arr;
//}
//public static List<double> MetricSelect<U>(this MetricList<U> collection, Func<U, double> selector)
// where U : class, IMetric<U>, new()
//{
// var coll = collection ?? [];
// var list = new List<double>(coll.Count);
// if (selector is not null)
// for (int i = 0; i < list.Count; i++)
// list[i] = selector(coll[i]);
// return list;
//}
//public static ObservableCollection<double> MetricSelect<U>(this MetricObservableCollection<U> collection, Func<U, double> selector)
// where U : class, IMetric<U>, new()
//{
// var coll = collection ?? [];
// var list = new List<double>(coll.Count);
// if (selector is not null)
// for (int i = 0; i < list.Count; i++)
// list[i] = selector(coll[i]);
// return new(list);
//}
//internal static C ForEachC<C, U>(this C? collection, Func<U, U>? Set)
// where C : IMetricCollection<U>, new() where U : class, IMetric<U>, new()
//{
// var nColl = (C)(collection ??= new()).CreateByInstanceU(collection.Count);
// if (Set is not null)
// for (int i = 0; i < nColl.Count; i++)
// nColl[i] = Set(nColl[i]);
// return nColl;
//}
//internal static double Protect_Value(this IMetric? metric) => metric is null ? 0d : metric._Value;
//public static U Min<U>(this U? T1, U? T2) where U : class, IMetric<U>, new() => (T1.Protect_Value() < T2.Protect_Value() ? T1 : T2).Protect();
//public static U Min<U>(this U T1, IEnumerable<U> units) where U : class, IMetric<U>, new() => (T1 ?? new()).Min((units ?? []).MaxBy(u => u.Protect_Value()));
//public static U Max<U>(this U? T1, U? T2) where U : class, IMetric<U>, new() => (T1.Protect_Value() > T2.Protect_Value() ? T1 : T2).Protect();
//public static U Max<U>(this U T1, IEnumerable<U> units) where U : class, IMetric<U>, new() => (T1 ?? new()).Max((units ?? []).MaxBy(u => u.Protect_Value()));
////internal static double ToDouble(this double number) => number;
////internal static double ToDouble(this double? number) => number ?? 0d;
////internal static double ToDouble<N>(this N number) where N : INumber<N> => Convert.ToDouble(number);
////internal static double ToDouble<N>(this N? number) where N : struct, INumber<N> => number is not null ? Convert.ToDouble(number) : 0d;
//internal static IEnumerable<U> MetricSelect<U>(this double[] nums, Func<double, U> selector) where U : class, IMetric<U>, new() => nums.Select(selector);
//internal static IEnumerable<U> MetricSelect<U>(this double?[] nums, Func<double, U> selector) where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//internal static IEnumerable<U> MetricSelect<U, N>(this N[] nums, Func<double, U> selector) where N : INumber<N> where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//internal static IEnumerable<U> MetricSelect<U, N>(this N?[] nums, Func<double, U> selector) where N : struct, INumber<N> where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//internal static IEnumerable<U> MetricSelect<U>(this IEnumerable<double> nums, Func<double, U> selector) where U : class, IMetric<U>, new() => nums.Select(selector);
//internal static IEnumerable<U> MetricSelect<U>(this IEnumerable<double?> nums, Func<double, U> selector) where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//internal static IEnumerable<U> MetricSelect<U, N>(this IEnumerable<N> nums, Func<double, U> selector) where N : INumber<N> where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//internal static IEnumerable<U> MetricSelect<U, N>(this IEnumerable<N?> nums, Func<double, U> selector) where N : struct, INumber<N> where U : class, IMetric<U>, new() => nums.Select(num => selector(num.ToDouble()));
//public static U Clone<U>(this U? metric) where U : class, IMetric<U>, new() => new() { _Value = metric.Protect_Value() };
//public static U Abs<U>(this U? metric) where U : class, IMetric<U>, new() => new() { _Value = Math.Abs(metric.Protect_Value()) };
///// <summary>C^2 = A^2 + B^2</summary>
///// <returns>C = (A^2 + B^2).Sqrt(2)</returns>
//public static U Hypotenuse<U>(this U? A, U? B) where U : class, IMetric<U>, new()
//{
// var a = A.Protect_Value();
// var b = B.Protect_Value();
// return new U() { _Value = Math.Sqrt(a * a + b * b) };
//}
///// <summary>C^2 = A^2 + B^2</summary>
///// <returns>B = (C^2 - A^2).Sqrt(2)</returns>
//public static U KatetFromHyp<U>(this U? A, U? C) where U : class, IMetric<U>, new()
//{
// var a = A.Protect_Value();
// var c = C.Protect_Value();
// return new U() { _Value = Math.Sqrt(c * c - a * a) };
//}
///// <summary>C^2 = A^2 + B^2</summary>
///// <returns>B = (C^2 - A^2).Sqrt(2)</returns>
//public static U KatetFromKatet<U>(this U? C, U? A) where U : class, IMetric<U>, new()
//{
// var a = A.Protect_Value();
// var c = C.Protect_Value();
// return new U() { _Value = Math.Sqrt(c * c - a * a) };
//}
//public static Area Pow(this Length? metric, double? val = 2) => new() { _Value = Math.Pow(metric.Protect_Value(), val ?? 2) };
//public static Length Sqrt(this Area? metric) => new() { _Value = Math.Sqrt(metric.Protect_Value()) };
//public static U MetricSum<U>(this IEnumerable<U> args) where U : IMetric, new() => new() { _Value = args?.Where(t => t is not null).Sum(m => m.Protect_Value()) ?? 0d };
//public static U MetricAverage<U>(this IEnumerable<U> args) where U : IMetric, new() => new() { _Value = args?.Average(m => m.Protect_Value()) ?? double.NaN };
//public static U MetricMax<U>(this IEnumerable<U> args) where U : IMetric, new() => new() { _Value = args.Max(m => m.Protect_Value()) };
//public static U MetricMin<U>(this IEnumerable<U> args) where U : IMetric, new() => new() { _Value = args.Min(m => m.Protect_Value()) };
//public static C MetricSum<C, U>(this IEnumerable<MetricCollection<C, U>> collections)
// where C : MetricCollection<C, U>, ICreateByCapacity, new() where U : class, IMetric<U>, new()
//{
// var cArr = collections.ToArray();
// C accumulator = (C)cArr.FirstOrDefault(new C());
// for (int i = 1; i < cArr.Length; i++)
// accumulator = accumulator.FuncByPairOrOneToMany(cArr[i], (a, b) => a + b, out C _);
// return accumulator;
//}
//public static C MetricAverage<C, U>(this IEnumerable<MetricCollection<C, U>> collections)
// where C : MetricCollection<C, U>, ICreateByCapacity, new() where U : class, IMetric<U>, new()
// => collections.Sum() / collections.Count();
//public static U MetricSumBy<TSource, U>(this IEnumerable<TSource> source, Func<TSource, U> selector)
// where U : class, IMetric<U>, new()
//{
// if (source is null) return new();
// if (selector is null) throw new ArgumentNullException("selector is null");
// return new() { _Value = source.Select(selector).Where(t => t is not null).Sum(t => t.Protect_Value()) };
//}
//public static U MetricAverageBy<TSource, U>(this IEnumerable<TSource> source, Func<TSource, U> selector)
// where U : class, IMetric<U>, new()
//{
// if (source is null) return new();
// if (selector is null) throw new ArgumentNullException("selector is null");
// return new() { _Value = source.Select(selector).Average(t => t.Protect_Value()) };
//}
//public static C MetricSumBy<TSource, C, U>(this IEnumerable<TSource> source, Func<TSource, MetricCollection<C, U>> selector)
// where C : MetricCollection<C, U>, ICreateByCapacity, new() where U : class, IMetric<U>, new()
//{
// if (source is null) return new();
// if (selector is null) throw new ArgumentNullException("selector is null");
// return source.Select(selector).Sum();
//}
//public static C MetricAverageBy<TSource, C, U>(this IEnumerable<TSource> source, Func<TSource, MetricCollection<C, U>> selector)
// where C : MetricCollection<C, U>, ICreateByCapacity, new() where U : class, IMetric<U>, new()
//{
// if (source is null) return new();
// if (selector is null) throw new ArgumentNullException("selector is null");
// return source.Select(selector).Average();
//}
//public static MetricArray<U> ToMetricArray<U>(this IEnumerable<U> source) where U : class, IMetric<U>, new() => new(source);
//public static MetricList<U> ToMetricList<U>(this IEnumerable<U> source) where U : class, IMetric<U>, new() => new(source);
}

View File

@@ -0,0 +1,56 @@
using System.Globalization;
namespace QWERTYkez.Mensura;
public class UnitJsonConverter<T> : JsonConverter<T> where T : struct, IMensuraUnit, IEquatable<T>
{
// Используем инвариантную культуру, чтобы разделителем всегда была точка (10.5, а не 10,5)
private static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
double double_Value;
if (reader.TokenType == JsonTokenType.String)
{
// Безопасно парсим double из строки с поддержкой точки как разделителя
if (!double.TryParse(reader.GetString(), NumberStyles.Float, Culture, out double_Value))
{
throw new JsonException($"Не удалось преобразовать строковое значение в double для метрики {nameof(T)}.");
}
}
else
{
// Прямое быстрое чтение числа из JSON
double_Value = reader.GetDouble();
}
return double_Value.ToUnit<T>();
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
// Записываем число напрямую в байтовый буфер без выделения памяти под строки
writer.WriteNumberValue(value.ToDouble());
}
public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
// Ключи JSON-объектов всегда должны быть строками.
// Форматируем double в строку с точкой, чтобы другие сервисы экосистемы прочитали её корректно.
// Формат "R" (Round-trip) гарантирует, что число не потеряет точность при обратном парсинге.
writer.WritePropertyName(value.ToDouble().ToString("R", Culture));
}
public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
string propertyName = reader.GetString()!;
if (!double.TryParse(propertyName, NumberStyles.Float, Culture, out double double_Value))
{
throw new JsonException($"Невалидное числовое значение в ключе свойства JSON: '{propertyName}' для метрики {nameof(T)}.");
}
return double_Value.ToUnit<T>();
}
}

View File

@@ -11,7 +11,8 @@ public readonly partial record struct Length
public static Length CentiMeter { get; } = new(LengthConv.CentiMeters.To(1));
[NotMapped, JsonIgnore] public double CentiMeters
[NotMapped, JsonIgnore]
public double CentiMeters
{
get => LengthConv.CentiMeters.From(_Value);
init
@@ -29,21 +30,24 @@ public readonly partial record struct Length
}
public static Length DeciMeter { get; } = new(LengthConv.DeciMeters.To(1));
[NotMapped, JsonIgnore] public double DeciMeters
[NotMapped, JsonIgnore]
public double DeciMeters
{
get => LengthConv.DeciMeters.From(_Value);
init => _Value = LengthConv.DeciMeters.To(value);
}
public static Length Meter { get; } = new(LengthConv.Meters.To(1));
[NotMapped, JsonIgnore] public double Meters
[NotMapped, JsonIgnore]
public double Meters
{
get => LengthConv.Meters.From(_Value);
init => _Value = LengthConv.Meters.To(value);
}
public static Length KiloMeter { get; } = new(LengthConv.KiloMeters.To(1));
[NotMapped, JsonIgnore] public double KiloMeters
[NotMapped, JsonIgnore]
public double KiloMeters
{
get => LengthConv.KiloMeters.From(_Value);
init => _Value = LengthConv.KiloMeters.To(value);
@@ -55,6 +59,39 @@ public readonly partial record struct Length
public Length AddDeciMeters(double value) => new(_Value + LengthConv.DeciMeters.To(value));
public Length AddMeters(double value) => new(_Value + LengthConv.Meters.To(value));
public Length AddKiloMeters(double value) => new(_Value + LengthConv.KiloMeters.To(value));
/// <summary>C^2 = this^2 + B^2</summary> <returns>C = (this^2 + B^2).Sqrt(2)</returns>
public Length HypFromLeg(Length B) => new(Math.Sqrt(_Value * _Value + B._Value * B._Value));
/// <summary>C^2 = this^2 + B^2</summary> <returns>C = (this^2 + B^2).Sqrt(2)</returns>
public Length HypFromLeg(Length? B)
{
double b = B is null ? 0d : B.Value._Value;
return new(Math.Sqrt(_Value * _Value + b * b));
}
/// <summary>C^2 = this^2 + B^2</summary> <returns>B = (C^2 - this^2).Sqrt(2)</returns>
public Length LegFromHyp(Length C) => new(Math.Sqrt(C._Value * C._Value - _Value * _Value));
/// <summary>C^2 = this^2 + B^2</summary> <returns>B = (C^2 - this^2).Sqrt(2)</returns>
public Length LegFromHyp(Length? C)
{
double c = C is null ? 0d : C.Value._Value;
return new(Math.Sqrt(c * c - _Value * _Value));
}
/// <summary>this^2 = A^2 + B^2</summary> <returns>B = (this^2 - A^2).Sqrt(2)</returns>
public Length LegFromLeg(Length A) => new(Math.Sqrt(_Value * _Value - A._Value * A._Value));
/// <summary>this^2 = A^2 + B^2</summary> <returns>B = (this^2 - A^2).Sqrt(2)</returns>
public Length LegFromLeg(Length? A)
{
double a = A is null ? 0d : A.Value._Value;
return new(Math.Sqrt(_Value * _Value - a * a));
}
}

View File

@@ -83,7 +83,7 @@ namespace QWERTYkez.Mensura.Units
result[i] = val * right[i];
return result;
}
public static Span<Mass> operator *(ReadOnlySpan<PogonMass> left, Length right)
public static Span<Mass> operator *(this ReadOnlySpan<PogonMass> left, Length right)
{
int len = left.Length;
if (len == 0) return [];
@@ -93,7 +93,7 @@ namespace QWERTYkez.Mensura.Units
result[i] = right * left[i];
return result;
}
public static Span<Mass> operator *(ReadOnlySpan<PogonMass> left, Length? right)
public static Span<Mass> operator *(this ReadOnlySpan<PogonMass> left, Length? right)
{
int len = left.Length;
if (len == 0) return [];
@@ -277,7 +277,7 @@ namespace QWERTYkez.Mensura.Units.Pogon
result[i] = val * right[i];
return result;
}
public static Span<Mass> operator *(ReadOnlySpan<Length> left, PogonMass right)
public static Span<Mass> operator *(this ReadOnlySpan<Length> left, PogonMass right)
{
int len = left.Length;
if (len == 0) return [];
@@ -287,7 +287,7 @@ namespace QWERTYkez.Mensura.Units.Pogon
result[i] = right * left[i];
return result;
}
public static Span<Mass> operator *(ReadOnlySpan<Length> left, PogonMass? right)
public static Span<Mass> operator *(this ReadOnlySpan<Length> left, PogonMass? right)
{
int len = left.Length;
if (len == 0) return [];

File diff suppressed because it is too large Load Diff

View File

@@ -11,4 +11,5 @@ global using System.Runtime.InteropServices;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using QWERTYkez.Mensura;
global using QWERTYkez.Mensura.Extensions;
global using QWERTYkez.Mensura.Units;