3560 lines
140 KiB
C#
3560 lines
140 KiB
C#
using System.Buffers;
|
||
using System.Runtime.Intrinsics;
|
||
using System.Runtime.Intrinsics.X86;
|
||
|
||
namespace QWERTYkez.Mensura;
|
||
|
||
public static partial class Extensions
|
||
{
|
||
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
|
||
|
||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
internal static unsafe 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 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);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public static double ToDouble<T>(this T unit)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
=> Unsafe.As<T, double>(ref unit);
|
||
|
||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||
public 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)]
|
||
public static T ToUnit<T>(this double val)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
=> Unsafe.As<double, T>(ref val);
|
||
|
||
|
||
// ==========================================
|
||
// Sum Average Max Min (не nullable)
|
||
// ==========================================
|
||
|
||
public static T Sum<T>(this ReadOnlySpan<T> units)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (Unsafe.SizeOf<T>() != sizeof(double))
|
||
{
|
||
throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD.");
|
||
}
|
||
|
||
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>
|
||
{
|
||
// 0. Защитная проверка для JIT (0 рантайм оверхеда)
|
||
if (Unsafe.SizeOf<T>() != sizeof(double))
|
||
{
|
||
throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD.");
|
||
}
|
||
|
||
// Если коллекция пустая, возвращаем структуру, содержащую 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 (Unsafe.SizeOf<T>() != sizeof(double))
|
||
{
|
||
throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD.");
|
||
}
|
||
|
||
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 (Unsafe.SizeOf<T>() != sizeof(double))
|
||
{
|
||
throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD.");
|
||
}
|
||
|
||
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);
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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); }
|
||
}
|
||
|
||
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)
|
||
// ==========================================
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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();
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
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.Sum(); // Подхватит 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); }
|
||
}
|
||
|
||
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>();
|
||
}
|
||
|
||
// ==========================================
|
||
|
||
|
||
|
||
// ==========================================
|
||
// CORE
|
||
// ==========================================
|
||
internal static void MultiplyCore<T>(ReadOnlySpan<T> source, double multiplicator, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (source.IsEmpty) return;
|
||
|
||
int len = source.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(source);
|
||
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;
|
||
}
|
||
}
|
||
internal static void DivideCore<T>(ReadOnlySpan<T> dividends, double divisor, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (dividends.IsEmpty) return;
|
||
|
||
int len = dividends.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(dividends);
|
||
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;
|
||
}
|
||
}
|
||
internal static void DivideCore<T>(double dividend, ReadOnlySpan<T> divisors, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (divisors.IsEmpty) return;
|
||
|
||
int len = divisors.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(divisors);
|
||
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);
|
||
}
|
||
}
|
||
internal static void PlusCore<T>(ReadOnlySpan<T> summands, double summand, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (summands.IsEmpty) return;
|
||
|
||
int len = summands.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(summands);
|
||
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;
|
||
}
|
||
}
|
||
internal static void MinusCore<T>(ReadOnlySpan<T> minuends, double subtrahend, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (minuends.IsEmpty) return;
|
||
|
||
int len = minuends.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(minuends);
|
||
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;
|
||
}
|
||
}
|
||
internal static void MinusCore<T>(double minuend, ReadOnlySpan<T> subtrahends, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (subtrahends.IsEmpty) return;
|
||
|
||
int len = subtrahends.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(subtrahends);
|
||
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);
|
||
}
|
||
}
|
||
|
||
internal static void PowCore<T>(ReadOnlySpan<T> source, int power, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (source.IsEmpty) return;
|
||
|
||
int len = source.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(source);
|
||
Span<double> dstDouble = MemoryMarshal.Cast<T, 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 PowCore<T>(ReadOnlySpan<T> source, double power, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (source.IsEmpty) return;
|
||
|
||
int len = source.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(source);
|
||
Span<double> dstDouble = MemoryMarshal.Cast<T, 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 SqrtCore<T>(ReadOnlySpan<T> source, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (source.IsEmpty) return;
|
||
|
||
int len = source.Length;
|
||
ReadOnlySpan<double> srcDouble = MemoryMarshal.Cast<T, double>(source);
|
||
Span<double> dstDouble = MemoryMarshal.Cast<T, 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));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
// ==========================================
|
||
// === MULTIPLY ===
|
||
// ==========================================
|
||
|
||
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];
|
||
MultiplyCore(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);
|
||
|
||
// === ReadOnlySpan ===
|
||
public static void Multiply<T>(this ReadOnlySpan<T> units, double multiplicator, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.MultiplyCore(units, multiplicator, destination);
|
||
}
|
||
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;
|
||
|
||
// Получаем прямые неуправляемые 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);
|
||
|
||
// === 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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.MultiplyCore(CollectionsMarshal.AsSpan(units), multiplicator, CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
|
||
[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);
|
||
MultiplyCore(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.AsSpan());
|
||
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
|
||
{
|
||
units.CopyTo(sharedArray, 0);
|
||
MultiplyCore(new ReadOnlySpan<T>(sharedArray, 0, count), 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);
|
||
|
||
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.AsSpan());
|
||
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);
|
||
|
||
|
||
|
||
|
||
// ==========================================
|
||
// === DIVIDE ===
|
||
// ==========================================
|
||
|
||
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];
|
||
DivideCore(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];
|
||
DivideCore(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;
|
||
}
|
||
|
||
// === ReadOnlySpan ===
|
||
public static void Divide<T>(this ReadOnlySpan<T> units, double divisor, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.DivideCore(units, divisor, destination);
|
||
}
|
||
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;
|
||
|
||
// Получаем прямые неуправляемые 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;
|
||
}
|
||
}
|
||
public static void Divide<T>(this double dividend, ReadOnlySpan<T> units, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.DivideCore(dividend, units, destination);
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
// === 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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.DivideCore(CollectionsMarshal.AsSpan(units), divisor, CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.DivideCore(dividend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
|
||
// === 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);
|
||
DivideCore(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.AsSpan());
|
||
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);
|
||
DivideCore(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.AsSpan());
|
||
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
|
||
{
|
||
units.CopyTo(sharedArray, 0);
|
||
DivideCore(new ReadOnlySpan<T>(sharedArray, 0, count), 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);
|
||
|
||
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.AsSpan());
|
||
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 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);
|
||
DivideCore(dividend, new ReadOnlySpan<T>(sharedArray, 0, count), 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 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.AsSpan());
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
|
||
// ==========================================
|
||
// === PLUS ===
|
||
// ==========================================
|
||
|
||
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];
|
||
PlusCore(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);
|
||
|
||
// === ReadOnlySpan ===
|
||
public static void Plus<T>(this ReadOnlySpan<T> units, double summand, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.PlusCore(units, summand, destination);
|
||
}
|
||
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);
|
||
|
||
// === 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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.PlusCore(CollectionsMarshal.AsSpan(units), summand, CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
|
||
[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);
|
||
PlusCore(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.AsSpan());
|
||
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
|
||
{
|
||
units.CopyTo(sharedArray, 0);
|
||
PlusCore(new ReadOnlySpan<T>(sharedArray, 0, count), 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);
|
||
|
||
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.AsSpan());
|
||
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);
|
||
|
||
|
||
|
||
|
||
// ==========================================
|
||
// === MINUS ===
|
||
// ==========================================
|
||
|
||
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];
|
||
MinusCore(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];
|
||
MinusCore(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;
|
||
}
|
||
|
||
// === ReadOnlySpan ===
|
||
public static void Minus<T>(this ReadOnlySpan<T> units, double subtrahend, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.MinusCore(units, subtrahend, destination);
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
public static void Minus<T>(this double minuend, ReadOnlySpan<T> units, Span<T> destination)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length > destination.Length)
|
||
throw new ArgumentException("Целевой буфер destination меньше исходного source.");
|
||
Extensions.MinusCore(minuend, units, destination);
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
|
||
// === 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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.MinusCore(CollectionsMarshal.AsSpan(units), subtrahend, CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
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 result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.MinusCore(minuend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
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];
|
||
}
|
||
|
||
// === 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);
|
||
MinusCore(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.AsSpan());
|
||
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);
|
||
MinusCore(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.AsSpan());
|
||
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.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);
|
||
MinusCore(new ReadOnlySpan<T>(sharedArray, 0, count), 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.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.AsSpan());
|
||
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 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);
|
||
MinusCore(minuend, new ReadOnlySpan<T>(sharedArray, 0, count), 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 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.AsSpan());
|
||
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);
|
||
}
|
||
|
||
|
||
|
||
|
||
// ==========================================
|
||
// === ЦЕЛАЯ СТЕПЕНЬ ===
|
||
// ==========================================
|
||
|
||
public static T[] Pow<T>(this T[] units, int power)
|
||
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];
|
||
Extensions.PowCore(units, power, result);
|
||
return result;
|
||
}
|
||
public static T?[] Pow<T>(this T?[] units, int power)
|
||
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++)
|
||
{
|
||
ref var item = ref result[i];
|
||
if (item.HasValue)
|
||
item = item.Value.ToDouble().QuickPow(power).ToUnit<T>();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// === ReadOnlySpan ===
|
||
public static Span<T> Pow<T>(this ReadOnlySpan<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units.Length == 0) return [];
|
||
var result = new T[units.Length];
|
||
Extensions.PowCore(units, power, result);
|
||
return result;
|
||
}
|
||
public static Span<T?> Pow<T>(this ReadOnlySpan<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
int len = units.Length;
|
||
if (len == 0) return [];
|
||
var result = new T?[len];
|
||
units.CopyTo(result);
|
||
for (int i = 0; i < len; i++)
|
||
{
|
||
ref var item = ref result[i];
|
||
if (item.HasValue) item = item.Value.ToDouble().QuickPow(power).ToUnit<T>();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// === List<Length> ===
|
||
public static List<T> Pow<T>(this List<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
int len = units.Count;
|
||
if (len == 0) return [];
|
||
|
||
var result = new List<T>(len);
|
||
result.SetCountUnsafe(len);
|
||
Extensions.PowCore(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result));
|
||
return result;
|
||
}
|
||
public static List<T?> Pow<T>(this List<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
int len = units.Count;
|
||
if (len == 0) return [];
|
||
|
||
var result = new List<T?>(units);
|
||
var dstSpan = CollectionsMarshal.AsSpan(result);
|
||
for (int i = 0; i < len; i++)
|
||
{
|
||
ref var item = ref dstSpan[i];
|
||
if (item.HasValue) item = item.Value.ToDouble().QuickPow(power).ToUnit<T>();
|
||
}
|
||
return result;
|
||
}
|
||
|
||
// === ICollection<Length> ===
|
||
public static ICollection<T> Pow<T>(this ICollection<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T[] array) return array.Pow(power);
|
||
if (units is List<T> list) return list.Pow(power);
|
||
|
||
ICollection<T> result = [];
|
||
foreach (var item in units)
|
||
result.Add(item.ToDouble().QuickPow(power).ToUnit<T>());
|
||
return result;
|
||
}
|
||
public static ICollection<T?> Pow<T>(this ICollection<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T?[] array) return array.Pow(power);
|
||
if (units is List<T?> list) return list.Pow(power);
|
||
|
||
ICollection<T?> result = [];
|
||
foreach (var item in units)
|
||
result.Add((item.HasValue ? item.Value.ToDouble().QuickPow(power) : 0d).ToUnit<T>());
|
||
return result;
|
||
}
|
||
|
||
// === IReadOnlyCollection<Length> ===
|
||
public static IReadOnlyCollection<T> Pow<T>(this IReadOnlyCollection<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T[] array) return array.Pow(power);
|
||
if (units is List<T> list) return list.Pow(power);
|
||
|
||
IReadOnlyCollection<T> result = [];
|
||
foreach (var item in units)
|
||
result.Add(item.ToDouble().QuickPow(power).ToUnit<T>());
|
||
return result;
|
||
}
|
||
public static IReadOnlyCollection<T?> Pow<T>(this IReadOnlyCollection<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T?[] array) return array.Pow(power);
|
||
if (units is List<T?> list) return list.Pow(power);
|
||
|
||
IReadOnlyCollection<T?> result = [];
|
||
foreach (var item in units)
|
||
result.Add((item.HasValue ? item.Value.ToDouble().QuickPow(power) : 0d).ToUnit<T>());
|
||
return result;
|
||
}
|
||
|
||
// === IEnumerable<T> + yeild ===
|
||
static IEnumerable<T> PowIterator<T>(IEnumerable<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
foreach (var item in units)
|
||
yield return (item.ToDouble().QuickPow(power)).ToUnit<T>();
|
||
}
|
||
static IEnumerable<T?> PowNullableIterator<T>(IEnumerable<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
foreach (var item in units)
|
||
yield return item.HasValue
|
||
? item.Value.ToDouble().QuickPow(power).ToUnit<T>() : null;
|
||
}
|
||
|
||
// === IEnumerable<Length> ===
|
||
public static IEnumerable<T> Pow<T>(this IEnumerable<T> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T[] array) return array.Pow(power);
|
||
if (units is List<T> list) return list.Pow(power);
|
||
if (units is ICollection<T> col) return col.Pow(power);
|
||
if (units is IReadOnlyCollection<T> roc) return roc.Pow(power);
|
||
else return PowIterator(units, power);
|
||
}
|
||
public static IEnumerable<T?> Pow<T>(this IEnumerable<T?> units, int power)
|
||
where T : struct, IMensuraUnit, IEquatable<T>
|
||
{
|
||
if (units is null) return null!;
|
||
if (units is T?[] array) return array.Pow(power);
|
||
if (units is List<T?> list) return list.Pow(power);
|
||
if (units is ICollection<T> col) return col.Pow(power);
|
||
if (units is IReadOnlyCollection<T> roc) return roc.Pow(power);
|
||
else return PowNullableIterator(units, power);
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
//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);
|
||
} |