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

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)); 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); get => LengthConv.CentiMeters.From(_Value);
init init
@@ -29,21 +30,24 @@ public readonly partial record struct Length
} }
public static Length DeciMeter { get; } = new(LengthConv.DeciMeters.To(1)); 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); get => LengthConv.DeciMeters.From(_Value);
init => _Value = LengthConv.DeciMeters.To(value); init => _Value = LengthConv.DeciMeters.To(value);
} }
public static Length Meter { get; } = new(LengthConv.Meters.To(1)); 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); get => LengthConv.Meters.From(_Value);
init => _Value = LengthConv.Meters.To(value); init => _Value = LengthConv.Meters.To(value);
} }
public static Length KiloMeter { get; } = new(LengthConv.KiloMeters.To(1)); 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); get => LengthConv.KiloMeters.From(_Value);
init => _Value = LengthConv.KiloMeters.To(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 AddDeciMeters(double value) => new(_Value + LengthConv.DeciMeters.To(value));
public Length AddMeters(double value) => new(_Value + LengthConv.Meters.To(value)); public Length AddMeters(double value) => new(_Value + LengthConv.Meters.To(value));
public Length AddKiloMeters(double value) => new(_Value + LengthConv.KiloMeters.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]; result[i] = val * right[i];
return result; 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; int len = left.Length;
if (len == 0) return []; if (len == 0) return [];
@@ -93,7 +93,7 @@ namespace QWERTYkez.Mensura.Units
result[i] = right * left[i]; result[i] = right * left[i];
return result; 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; int len = left.Length;
if (len == 0) return []; if (len == 0) return [];
@@ -277,7 +277,7 @@ namespace QWERTYkez.Mensura.Units.Pogon
result[i] = val * right[i]; result[i] = val * right[i];
return result; 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; int len = left.Length;
if (len == 0) return []; if (len == 0) return [];
@@ -287,7 +287,7 @@ namespace QWERTYkez.Mensura.Units.Pogon
result[i] = right * left[i]; result[i] = right * left[i];
return result; 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; int len = left.Length;
if (len == 0) return []; 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;
global using System.Text.Json.Serialization; global using System.Text.Json.Serialization;
global using QWERTYkez.Mensura; global using QWERTYkez.Mensura;
global using QWERTYkez.Mensura.Extensions;
global using QWERTYkez.Mensura.Units; global using QWERTYkez.Mensura.Units;