diff --git a/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs b/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs index 22f10dd..db3af7d 100644 --- a/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs +++ b/QWERTYkez.Mensura.Generator/UnitOperatorsGenerator.cs @@ -92,14 +92,12 @@ namespace QWERTYkez.Mensura // заменив {typeName} на {typeName}. string skeleton = @" global using {typeName} = QWERTYkez.Mensura.Units.{typeName}; -global using {typeName}Extensions = QWERTYkez.Mensura.Units.{typeName}Extension; -global using {typeName}Converter = QWERTYkez.Mensura.Units.{typeName}Converter; - -using System.Globalization; using System.Runtime.Serialization; namespace QWERTYkez.Mensura.Units; +public class {typeName}Converter : UnitJsonConverter<{typeName}> { } + [JsonConverter(typeof({typeName}Converter))] public readonly partial record struct {typeName} : IMensuraUnit<{typeName}>, IEquatable<{typeName}>, IMensuraUnit { @@ -118,8 +116,13 @@ public readonly partial record struct {typeName} : IMensuraUnit<{typeName}>, IEq [JsonIgnore, IgnoreDataMember] public bool IsPositive => _Value >= 0; [JsonIgnore, IgnoreDataMember] public bool IsGreaterThanZero => _Value > 0; - [JsonIgnore, IgnoreDataMember] public bool IsNegative => _Value < 0; + [JsonIgnore, IgnoreDataMember] public bool IsNegative => double.IsNegative(_Value); [JsonIgnore, IgnoreDataMember] public bool IsZero => _Value == 0; + [JsonIgnore, IgnoreDataMember] public bool IsNaN => double.IsNaN(_Value); + [JsonIgnore, IgnoreDataMember] public bool IsFinite => double.IsFinite(_Value); + [JsonIgnore, IgnoreDataMember] public bool IsInfinity => double.IsInfinity(_Value); + [JsonIgnore, IgnoreDataMember] public bool IsPositiveInfinity => double.IsPositiveInfinity(_Value); + [JsonIgnore, IgnoreDataMember] public bool IsNegativeInfinity => double.IsNegativeInfinity(_Value); public static {typeName} Zero { get; } = new(0d); @@ -267,1515 +270,18 @@ public readonly partial record struct {typeName} : IMensuraUnit<{typeName}>, IEq public static {typeName} operator /({typeName} T1, UInt128? T2) => T1 / T2.ToDouble(); #endif - public static explicit operator {typeName}(double val) => new(val); + public static explicit operator {typeName}(double val) => Unsafe.As(ref val); public static explicit operator double({typeName} unit) => unit._Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public {typeName} Abs() => new(Math.Abs(_Value)); - /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) - public {typeName} HypFromLeg({typeName} B) => new(Math.Sqrt(_Value * _Value + B._Value * B._Value)); - - /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) - public {typeName} HypFromLeg({typeName}? B) - { - double b = B is null ? 0d : B.Value._Value; - return new(Math.Sqrt(_Value * _Value + b * b)); - } - - /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) - public {typeName} LegFromHyp({typeName} C) => new(Math.Sqrt(C._Value * C._Value - _Value * _Value)); - - /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) - public {typeName} LegFromHyp({typeName}? C) - { - double c = C is null ? 0d : C.Value._Value; - return new(Math.Sqrt(c * c - _Value * _Value)); - } - - /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) - public {typeName} LegFromLeg({typeName} A) => new(Math.Sqrt(_Value * _Value - A._Value * A._Value)); - - /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) - public {typeName} LegFromLeg({typeName}? A) - { - double a = A is null ? 0d : A.Value._Value; - return new(Math.Sqrt(_Value * _Value - a * a)); - } -} - -public static class {typeName}Extension -{ - internal static double ToDouble(this {typeName}? unit) => unit is null ? 0d : unit.Value._Value; - - public static {typeName} MetricSum(this IEnumerable<{typeName}> units) => new(units?.Sum(m => m._Value) ?? 0d); - public static {typeName} MetricAverage(this IEnumerable<{typeName}> units) => new(units?.Average(m => m._Value) ?? double.NaN); - public static {typeName} MetricMax(this IEnumerable<{typeName}> units) => new(units?.Max(m => m._Value) ?? double.MinValue); - public static {typeName} MetricMin(this IEnumerable<{typeName}> units) => new(units?.Min(m => m._Value) ?? double.MaxValue); - - public static {typeName} MetricSum(this IEnumerable<{typeName}?> units) => new(units?.Sum(m => m.ToDouble()) ?? 0d); - public static {typeName} MetricAverage(this IEnumerable<{typeName}?> units) => new(units?.Average(m => m.ToDouble()) ?? double.NaN); - public static {typeName} MetricMax(this IEnumerable<{typeName}?> units) => new(units?.Max(m => m.ToDouble()) ?? double.MinValue); - public static {typeName} MetricMin(this IEnumerable<{typeName}?> units) => new(units?.Min(m => m.ToDouble()) ?? double.MaxValue); - - - internal static void MultiplyCore(ReadOnlySpan<{typeName}> source, double value, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(value); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = vector * vectorized_Value; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * value; - } - } - internal static void DivideCore(ReadOnlySpan<{typeName}> source, double divisor, Span<{typeName}> destination) - { - // 1. Проверка на ноль - if (divisor == 0d || double.IsNaN(divisor)) - throw new DivideByZeroException(""Делитель не может быть равен нулю.""); - - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(divisor); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = vector / vectorized_Value; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) / divisor; - } - } - internal static void DivideCore(double dividend, ReadOnlySpan<{typeName}> source, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(dividend); - var zeroVector = Vector.Zero; // Вектор из нулей для сравнения - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - - // БЫСТРАЯ ПРОВЕРКА: Есть ли хотя бы один 0.0 в текущем векторе? - if (Vector.EqualsAny(vector, zeroVector)) - { - throw new DivideByZeroException($""Обнаружен делитель, равный нулю, в районе индексов {i}..{i + vectorSize - 1}.""); - } - - var multiplied = vectorized_Value / vector; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - // Хвостовой цикл - for (; i < len; i++) - { - double divisor = Unsafe.Add(ref srcRef, i); - if (divisor == 0.0) - { - throw new DivideByZeroException($""Обнаружен делитель, равный нулю, в индексе {i}.""); - } - Unsafe.Add(ref dstRef, i) = dividend / divisor; - } - } - internal static void PlusCore(ReadOnlySpan<{typeName}> source, double summand, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(summand); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = vector + vectorized_Value; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) + summand; - } - } - internal static void MinusCore(ReadOnlySpan<{typeName}> source, double subtrahend, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(subtrahend); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = vector - vectorized_Value; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) - subtrahend; - } - } - internal static void MinusCore(double minuend, ReadOnlySpan<{typeName}> source, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - - var vectorized_Value = new Vector(minuend); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = vectorized_Value - vector; - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = minuend - Unsafe.Add(ref srcRef, i); - } - } - - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - internal static void PowCore(ReadOnlySpan<{typeName}> source, int power, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); - int vectorSize = Vector.Count; - int i = 0; - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - var vector = new Vector(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.Add(ref srcRef, i), vectorSize)); - var resultVector = VectorPow(vector, power); - resultVector.CopyTo(MemoryMarshal.CreateSpan(ref Unsafe.Add(ref dstRef, i), vectorSize)); - } - - // Приватный SIMD-метод быстрого возведения в степень - [MethodImpl(MethodImplOptions.AggressiveInlining)] - static Vector VectorPow(Vector baseVector, int exp) - { - if (exp == 0) return Vector.One; - if (exp == 1) return baseVector; - - if (exp < 0) - { - baseVector = Vector.One / baseVector; - exp = -exp; // Внимание: может переполниться при int.Min_Value, но для степеней это редчайший кейс - } - - var result = Vector.One; - var currentBase = baseVector; - - while (exp > 0) - { - if ((exp & 1) == 1) - { - result *= currentBase; - } - currentBase *= currentBase; - exp >>= 1; - } - - return result; - } - - for (; i < len; i++) - Unsafe.Add(ref dstRef, i) = Math.Pow(Unsafe.Add(ref srcRef, i), power); - } - [MethodImpl(MethodImplOptions.AggressiveOptimization)] - internal static unsafe void PowCore(ReadOnlySpan<{typeName}> source, double power, Span<{typeName}> destination) - { - int len = source.Length; - if (len == 0) return; - - fixed (double* pSrc = MemoryMarshal.Cast<{typeName}, double>(source)) - fixed (double* pDst = MemoryMarshal.Cast<{typeName}, double>(destination)) - { - double* pCurrentSrc = pSrc; - double* pCurrentDst = pDst; - double* pEnd = pSrc + len; - - while (pCurrentSrc <= pEnd - 4) - { - pCurrentDst[0] = Math.Pow(pCurrentSrc[0], power); - pCurrentDst[1] = Math.Pow(pCurrentSrc[1], power); - pCurrentDst[2] = Math.Pow(pCurrentSrc[2], power); - pCurrentDst[3] = Math.Pow(pCurrentSrc[3], power); - - pCurrentSrc += 4; - pCurrentDst += 4; - } - - while (pCurrentSrc < pEnd) - { - *pCurrentDst = Math.Pow(*pCurrentSrc, power); - - pCurrentSrc++; - pCurrentDst++; - } - } - } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void SqrtCore(ReadOnlySpan<{typeName}> source, Span<{typeName}> destination) - { - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast<{typeName}, double>(source); - Span dstDouble = MemoryMarshal.Cast<{typeName}, double>(destination); + internal R Pow2_Internal() where R : struct, IMensuraUnit, IEquatable => (_Value * _Value).ToUnit(); - int vectorSize = Vector.Count; - int i = 0; - - ref double srcRef = ref MemoryMarshal.GetReference(srcDouble); - ref double dstRef = ref MemoryMarshal.GetReference(dstDouble); - - int SIMDEnd = len - (len % vectorSize); - for (; i < SIMDEnd; i += vectorSize) - { - ref double currentSrc = ref Unsafe.Add(ref srcRef, i); - ReadOnlySpan srcWindow = MemoryMarshal.CreateReadOnlySpan(ref currentSrc, vectorSize); - var vector = new Vector(srcWindow); - var multiplied = Vector.SquareRoot(vector); - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Span dstWindow = MemoryMarshal.CreateSpan(ref currentDst, vectorSize); - multiplied.CopyTo(dstWindow); - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i)); - } - } - - // ========================================== - // === MULTIPLY === - // ========================================== - - public static {typeName}[] Multiply(this {typeName}[] units, double multiplicator) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - MultiplyCore(units, multiplicator, result); - return result; - } - public static {typeName}[] Multiply(this double multiplicator, {typeName}[] units) - => units.Multiply(multiplicator); - public static {typeName}?[] Multiply(this {typeName}?[] units, double multiplicator) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value * multiplicator); - } - return result; - } - public static {typeName}?[] Multiply(this double multiplicator, {typeName}?[] units) - => units.Multiply(multiplicator); - - // === ReadOnlySpan === - public static Span<{typeName}> Multiply(this ReadOnlySpan<{typeName}> units, double multiplicator) - { - if (units.Length == 0) return []; - Span<{typeName}> result = new {typeName}[units.Length]; - MultiplyCore(units, multiplicator, result); - return result; - } - public static Span<{typeName}> Multiply(this double multiplicator, ReadOnlySpan<{typeName}> units) - => units.Multiply(multiplicator); - public static Span<{typeName}?> Multiply(this ReadOnlySpan<{typeName}?> units, double multiplicator) - { - if (units.Length == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value * multiplicator); - } - return result; - } - public static Span<{typeName}?> Multiply(this double multiplicator, ReadOnlySpan<{typeName}?> units) - => units.Multiply(multiplicator); - - // === List === - public static List<{typeName}> Multiply(this List<{typeName}> units, double multiplicator) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - MultiplyCore(CollectionsMarshal.AsSpan(units), multiplicator, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}> Multiply(this double multiplicator, List<{typeName}> units) - => units.Multiply(multiplicator); - public static List<{typeName}?> Multiply(this List<{typeName}?> units, double multiplicator) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value * multiplicator); - } - return result; - } - public static List<{typeName}?> Multiply(this double multiplicator, List<{typeName}?> units) - => units.Multiply(multiplicator); - - // === ICollection === - public static Tcoll Multiply(this ICollection<{typeName}> units, double multiplicator) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item._Value * multiplicator)); - return tColl; - } - public static Tcoll Multiply(this double multiplicator, ICollection<{typeName}> units) - where Tcoll : class, ICollection<{typeName}>, new() => Multiply(units, multiplicator); - public static Tcoll Multiply(this ICollection<{typeName}?> units, double multiplicator) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? item.Value._Value * multiplicator : 0d)); - return tColl; - } - public static Tcoll Multiply(this double multiplicator, ICollection<{typeName}?> units) - where Tcoll : class, ICollection<{typeName}?>, new() => Multiply(units, multiplicator); - - // === IEnumerable === - public static IEnumerable<{typeName}> Multiply(this IEnumerable<{typeName}> units, double multiplicator) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(item._Value * multiplicator); - } - public static IEnumerable<{typeName}> Multiply(this double multiplicator, IEnumerable<{typeName}> units) - => units.Multiply(multiplicator); - public static IEnumerable<{typeName}?> Multiply(this IEnumerable<{typeName}?> units, double multiplicator) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(item.Value._Value * multiplicator) : null; - } - public static IEnumerable<{typeName}?> Multiply(this double multiplicator, IEnumerable<{typeName}?> units) - => units.Multiply(multiplicator); - - // ========================================== - // === DIVIDE === - // ========================================== - - public static {typeName}[] Divide(this {typeName}[] units, double divisor) - { - if (units is null) return null!; - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) - throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - - var result = new {typeName}[units.Length]; - DivideCore(units, divisor, result); - return result; - } - public static {typeName}[] Divide(this double dividend, {typeName}[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - DivideCore(dividend, units, result); // Вызов зеркального ядра (число / массив) - return result; - } - public static {typeName}?[] Divide(this {typeName}?[] units, double divisor) - { - if (units is null) return null!; - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value / divisor); - } - return result; - } - public static {typeName}?[] Divide(this double dividend, {typeName}?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(dividend / item.Value._Value); - } - return result; - } - - // === ReadOnlySpan === - public static Span<{typeName}> Divide(this ReadOnlySpan<{typeName}> units, double divisor) - { - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - var result = new {typeName}[units.Length]; - DivideCore(units, divisor, result); - return result; - } - public static Span<{typeName}> Divide(this double dividend, ReadOnlySpan<{typeName}> units) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - DivideCore(dividend, units, result); - return result; - } - public static Span<{typeName}?> Divide(this ReadOnlySpan<{typeName}?> units, double divisor) - { - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) - throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value / divisor); - } - return result; - } - public static Span<{typeName}?> Divide(this double dividend, ReadOnlySpan<{typeName}?> units) - { - if (units.Length == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(dividend / item.Value._Value); - } - return result; - } - - // === List === - public static List<{typeName}> Divide(this List<{typeName}> units, double divisor) - { - if (units is null) return null!; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - DivideCore(CollectionsMarshal.AsSpan(units), divisor, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}> Divide(this double dividend, List<{typeName}> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - DivideCore(dividend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}?> Divide(this List<{typeName}?> units, double divisor) - { - if (units is null) return null!; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException(""Делитель не может быть 0 или NaN."", nameof(divisor)); - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value / divisor); - } - return result; - } - public static List<{typeName}?> Divide(this double dividend, List<{typeName}?> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(dividend / item.Value._Value); - } - return result; - } - - // === ICollection === - public static Tcoll Divide(this ICollection<{typeName}> units, double divisor) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item._Value / divisor)); - return tColl; - } - public static Tcoll Divide(this double dividend, ICollection<{typeName}> units) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(dividend / item._Value)); - return tColl; - } - public static Tcoll Divide(this ICollection<{typeName}?> units, double divisor) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? item.Value._Value / divisor : 0d)); - return tColl; - } - public static Tcoll Divide(this double dividend, ICollection<{typeName}?> units) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? dividend / item.Value._Value : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable<{typeName}> Divide(this IEnumerable<{typeName}> units, double divisor) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(item._Value * divisor); - } - public static IEnumerable<{typeName}> Divide(this double dividend, IEnumerable<{typeName}> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(dividend / item._Value); - } - public static IEnumerable<{typeName}?> Divide(this IEnumerable<{typeName}?> units, double divisor) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(item.Value._Value * divisor) : null; - } - public static IEnumerable<{typeName}?> Divide(this double dividend, IEnumerable<{typeName}?> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(dividend / item.Value._Value) : null; - } - - // ========================================== - // === PLUS === - // ========================================== - - public static {typeName}[] Plus(this {typeName}[] units, double summand) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - PlusCore(units, summand, result); - return result; - } - public static {typeName}[] Plus(this double summand, {typeName}[] units) - => units.Plus(summand); - public static {typeName}?[] Plus(this {typeName}?[] units, double summand) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value + summand); - } - return result; - } - public static {typeName}?[] Plus(this double summand, {typeName}?[] units) - => units.Plus(summand); - - // === ReadOnlySpan === - public static Span<{typeName}> Plus(this ReadOnlySpan<{typeName}> units, double summand) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - PlusCore(units, summand, result); - return result; - } - public static Span<{typeName}> Plus(this double summand, ReadOnlySpan<{typeName}> units) - => units.Plus(summand); - public static Span<{typeName}?> Plus(this ReadOnlySpan<{typeName}?> units, double summand) - { - if (units.Length == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value + summand); - } - return result; - } - public static Span<{typeName}?> Plus(this double summand, ReadOnlySpan<{typeName}?> units) - => units.Multiply(summand); - - // === List === - public static List<{typeName}> Plus(this List<{typeName}> units, double summand) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - PlusCore(CollectionsMarshal.AsSpan(units), summand, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}> Plus(this double summand, List<{typeName}> units) - => units.Plus(summand); - public static List<{typeName}?> Plus(this List<{typeName}?> units, double summand) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value + summand); - } - return result; - } - public static List<{typeName}?> Plus(this double summand, List<{typeName}?> units) - => units.Plus(summand); - - // === ICollection === - public static Tcoll Plus(this ICollection<{typeName}> units, double summand) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item._Value + summand)); - return tColl; - } - public static Tcoll Plus(this double summand, ICollection<{typeName}> units) - where Tcoll : class, ICollection<{typeName}>, new() => Plus(units, summand); - public static Tcoll Plus(this ICollection<{typeName}?> units, double summand) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? item.Value._Value + summand : 0d)); - return tColl; - } - public static Tcoll Plus(this double summand, ICollection<{typeName}?> units) - where Tcoll : class, ICollection<{typeName}?>, new() => Plus(units, summand); - - // === IEnumerable === - public static IEnumerable<{typeName}> Plus(this IEnumerable<{typeName}> units, double summand) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(item._Value + summand); - } - public static IEnumerable<{typeName}> Plus(this double summand, IEnumerable<{typeName}> units) - => units.Plus(summand); - public static IEnumerable<{typeName}?> Plus(this IEnumerable<{typeName}?> units, double summand) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(item.Value._Value + summand) : null; - } - public static IEnumerable<{typeName}?> Plus(this double summand, IEnumerable<{typeName}?> units) - => units.Plus(summand); - - // ========================================== - // === MINUS === - // ========================================== - - public static {typeName}[] Minus(this {typeName}[] units, double subtrahend) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - MinusCore(units, subtrahend, result); - return result; - } - public static {typeName}[] Minus(this double minuend, {typeName}[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - MinusCore(minuend, units, result); // Вызов зеркального ядра (число - массив) - return result; - } - public static {typeName}?[] Minus(this {typeName}?[] units, double subtrahend) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value - subtrahend); - } - return result; - } - public static {typeName}?[] Minus(this double minuend, {typeName}?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(minuend - item.Value._Value); - } - return result; - } - - // === ReadOnlySpan === - public static Span<{typeName}> Minus(this ReadOnlySpan<{typeName}> units, double subtrahend) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - MinusCore(units, subtrahend, result); - return result; - } - public static Span<{typeName}> Minus(this double minuend, ReadOnlySpan<{typeName}> units) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - MinusCore(minuend, units, result); - return result; - } - public static Span<{typeName}?> Minus(this ReadOnlySpan<{typeName}?> units, double subtrahend) - { - if (units.Length == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value - subtrahend); - } - return result; - } - public static Span<{typeName}?> Minus(this double minuend, ReadOnlySpan<{typeName}?> units) - { - if (units.Length == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(minuend - item.Value._Value); - } - return result; - } - - // === List === - public static List<{typeName}> Minus(this List<{typeName}> units, double subtrahend) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - MinusCore(CollectionsMarshal.AsSpan(units), subtrahend, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}> Minus(this double minuend, List<{typeName}> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - MinusCore(minuend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}?> Minus(this List<{typeName}?> units, double subtrahend) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value - subtrahend); - } - return result; - } - public static List<{typeName}?> Minus(this double minuend, List<{typeName}?> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(minuend - item.Value._Value); - } - return result; - } - - // === ICollection === - public static Tcoll Minus(this ICollection<{typeName}> units, double subtrahend) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item._Value - subtrahend)); - return tColl; - } - public static Tcoll Minus(this double minuend, ICollection<{typeName}> units) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(minuend - item._Value)); - return tColl; - } - public static Tcoll Minus(this ICollection<{typeName}?> units, double subtrahend) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? item.Value._Value - subtrahend : 0d)); - return tColl; - } - public static Tcoll Minus(this double minuend, ICollection<{typeName}?> units) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? minuend - item.Value._Value : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable<{typeName}> Minus(this IEnumerable<{typeName}> units, double subtrahend) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(item._Value - subtrahend); - } - public static IEnumerable<{typeName}> Minus(this double minuend, IEnumerable<{typeName}> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(minuend - item._Value); - } - public static IEnumerable<{typeName}?> Minus(this IEnumerable<{typeName}?> units, double subtrahend) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(item.Value._Value - subtrahend) : null; - } - public static IEnumerable<{typeName}?> Minus(this double minuend, IEnumerable<{typeName}?> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(minuend - item.Value._Value) : null; - } - - // ========================================== - // ЦЕЛАЯ СТЕПЕНЬ (int power) - // ========================================== - - public static {typeName}[] Pow(this {typeName}[] units, int power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - PowCore(units, power, result); - return result; - } - public static {typeName}?[] Pow(this {typeName}?[] units, int power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - // Используем тот же быстрый QuickPow, что и в ядре, чтобы не вызывать тяжелый Math.Pow - item = new {typeName}(item.Value._Value.QuickPow(power)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span<{typeName}> Pow(this ReadOnlySpan<{typeName}> units, int power) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - PowCore(units, power, result); - return result; - } - public static Span<{typeName}?> Pow(this ReadOnlySpan<{typeName}?> units, int power) - { - int len = units.Length; - if (len == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value.QuickPow(power)); - } - return result; - } - - // === List === - public static List<{typeName}> Pow(this List<{typeName}> units, int power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - PowCore(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}?> Pow(this List<{typeName}?> units, int power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(item.Value._Value.QuickPow(power)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection<{typeName}> units, int power) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item._Value.QuickPow(power))); - return tColl; - } - public static Tcoll Pow(this ICollection<{typeName}?> units, int power) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? item.Value._Value.QuickPow(power) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable<{typeName}> Pow(this IEnumerable<{typeName}> units, int power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(item._Value.QuickPow(power)); - } - public static IEnumerable<{typeName}?> Pow(this IEnumerable<{typeName}?> units, int power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(item.Value._Value.QuickPow(power)) : null; - } - - // ========================================== - // ДРОБНАЯ СТЕПЕНЬ (double power) - // ========================================== - - public static {typeName}[] Pow(this {typeName}[] units, double power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - PowCore(units, power, result); - return result; - } - public static {typeName}?[] Pow(this {typeName}?[] units, double power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - item = new {typeName}(Math.Pow(item.Value._Value, power)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span<{typeName}> Pow(this ReadOnlySpan<{typeName}> units, double power) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - PowCore(units, power, result); - return result; - } - public static Span<{typeName}?> Pow(this ReadOnlySpan<{typeName}?> units, double power) - { - int len = units.Length; - if (len == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(Math.Pow(item.Value._Value, power)); - } - return result; - } - - // === List === - public static List<{typeName}> Pow(this List<{typeName}> units, double power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - PowCore(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}?> Pow(this List<{typeName}?> units, double power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(Math.Pow(item.Value._Value, power)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection<{typeName}> units, double power) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(Math.Pow(item._Value, power))); - return tColl; - } - public static Tcoll Pow(this ICollection<{typeName}?> units, double power) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? Math.Pow(item.Value._Value, power) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable<{typeName}> Pow(this IEnumerable<{typeName}> units, double power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(Math.Pow(item._Value, power)); - } - public static IEnumerable<{typeName}?> Pow(this IEnumerable<{typeName}?> units, double power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(Math.Pow(item.Value._Value, power)) : null; - } - - - // ========================================== - // КВАДРАТНЫЙ КОРЕНЬ (Sqrt) - // ========================================== - - public static {typeName}[] Sqrt(this {typeName}[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new {typeName}[units.Length]; - SqrtCore(units, result); - return result; - } - public static {typeName}?[] Sqrt(this {typeName}?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = ({typeName}?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - item = new {typeName}(Math.Sqrt(item.Value._Value)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span<{typeName}> Sqrt(this ReadOnlySpan<{typeName}> units) - { - if (units.Length == 0) return []; - var result = new {typeName}[units.Length]; - SqrtCore(units, result); - return result; - } - public static Span<{typeName}?> Sqrt(this ReadOnlySpan<{typeName}?> units) - { - int len = units.Length; - if (len == 0) return []; - Span<{typeName}?> result = new {typeName}?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new {typeName}(Math.Sqrt(item.Value._Value)); - } - return result; - } - - // === List === - public static List<{typeName}> Sqrt(this List<{typeName}> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}>(len); - result.SetCountUnsafe(len); - SqrtCore(CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List<{typeName}?> Sqrt(this List<{typeName}?> units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List<{typeName}?>(units); - Span<{typeName}?> dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new {typeName}(Math.Sqrt(item.Value._Value)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection<{typeName}> units) - where Tcoll : class, ICollection<{typeName}>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(Math.Sqrt(item._Value))); - return tColl; - } - public static Tcoll Pow(this ICollection<{typeName}?> units) - where Tcoll : class, ICollection<{typeName}?>, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List<{typeName}> list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new {typeName}(item.HasValue ? Math.Sqrt(item.Value._Value) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable<{typeName}> Pow(this IEnumerable<{typeName}> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new {typeName}(Math.Sqrt(item._Value)); - } - public static IEnumerable<{typeName}?> Pow(this IEnumerable<{typeName}?> units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new {typeName}(Math.Sqrt(item.Value._Value)) : null; - } -} - -public class {typeName}Converter : JsonConverter<{typeName}> -{ - // Используем инвариантную культуру, чтобы разделителем всегда была точка (10.5, а не 10,5) - private static readonly CultureInfo Culture = CultureInfo.InvariantCulture; - - public override {typeName} Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - double double_Value; - - if (reader.TokenType == JsonTokenType.String) - { - // Безопасно парсим double из строки с поддержкой точки как разделителя - if (!double.TryParse(reader.GetString(), NumberStyles.Float, Culture, out double_Value)) - { - throw new JsonException($""Не удалось преобразовать строковое значение в double для метрики {nameof({typeName})}.""); - } - } - else - { - // Прямое быстрое чтение числа из JSON - double_Value = reader.GetDouble(); - } - - return new(double_Value); - } - - public override void Write(Utf8JsonWriter writer, {typeName} value, JsonSerializerOptions options) - { - // Записываем число напрямую в байтовый буфер без выделения памяти под строки - writer.WriteNumberValue(value._Value); - } - - public override void WriteAsPropertyName(Utf8JsonWriter writer, {typeName} value, JsonSerializerOptions options) - { - // Ключи JSON-объектов всегда должны быть строками. - // Форматируем double в строку с точкой, чтобы другие сервисы экосистемы прочитали её корректно. - // Формат ""R"" (Round-trip) гарантирует, что число не потеряет точность при обратном парсинге. - writer.WritePropertyName(value._Value.ToString(""R"", Culture)); - } - - public override {typeName} ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - string propertyName = reader.GetString()!; - - if (!double.TryParse(propertyName, NumberStyles.Float, Culture, out double double_Value)) - { - throw new JsonException($""Невалидное числовое значение в ключе свойства JSON: '{propertyName}' для метрики {nameof({typeName})}.""); - } - - return new(double_Value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal R Sqrt_Internal() where R : struct, IMensuraUnit, IEquatable => Math.Sqrt(_Value).ToUnit(); } "; return skeleton.Replace("{typeName}", typeName).Replace("{ns}", ns); diff --git a/QWERTYkez.Mensura/Extensions.cs b/QWERTYkez.Mensura/Extensions.cs deleted file mode 100644 index 9d0dd8e..0000000 --- a/QWERTYkez.Mensura/Extensions.cs +++ /dev/null @@ -1,3976 +0,0 @@ -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(this List list, int count) - { - // Берем адрес управляемого объекта List в памяти - // Объект передается по ref-ссылке, преобразуется в указатель - ref var mimic = ref Unsafe.As, ListLayoutMimic>(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(this T unit) - where T : struct, IMensuraUnit, IEquatable - => Unsafe.As(ref unit); - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static double ToDouble(this T? unit) - where T : struct, IMensuraUnit, IEquatable - { - T actual = unit.GetValueOrDefault(); - return Unsafe.As(ref actual); - } - - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T ToUnit(this double val) - where T : struct, IMensuraUnit, IEquatable - => Unsafe.As(ref val); - - - // ========================================== - // Sum Average Max Min (не nullable) - // ========================================== - - public static T Sum(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (Unsafe.SizeOf() != sizeof(double)) - { - throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD."); - } - - if (units.IsEmpty) return default; - - ReadOnlySpan values = MemoryMarshal.Cast(units); - double sum = 0; - int i = 0; - - // 1. ПУТЬ AVX2 (Intel/AMD) — Самый быстрый, обрабатывает по 4 элемента через Unsafe - if (Avx2.IsSupported && values.Length >= 4) - { - Vector256 vSum = Vector256.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>(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 vSum = Vector256.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>(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.Count) - { - int vCount = Vector.Count; - Vector vSum = Vector.Zero; - - for (; i <= values.Length - vCount; i += vCount) - { - var v = new Vector(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(ref sum); - } - public static T Average(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - // 0. Защитная проверка для JIT (0 рантайм оверхеда) - if (Unsafe.SizeOf() != sizeof(double)) - { - throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD."); - } - - // Если коллекция пустая, возвращаем структуру, содержащую double.NaN - if (units.IsEmpty) - { - double nan = double.NaN; - return Unsafe.As(ref nan); - } - - ReadOnlySpan values = MemoryMarshal.Cast(units); - double sum = 0; - int i = 0; - - // 1. ПУТЬ AVX2 (Intel/AMD) - if (Avx2.IsSupported && values.Length >= 4) - { - Vector256 vSum = Vector256.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>(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 vSum = Vector256.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>(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.Count) - { - int vCount = Vector.Count; - Vector vSum = Vector.Zero; - - for (; i <= values.Length - vCount; i += vCount) - { - var v = new Vector(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(ref average); - } - public static T Max(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (Unsafe.SizeOf() != sizeof(double)) - { - throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD."); - } - - if (units.IsEmpty) - { - double minVal = double.MinValue; - return Unsafe.As(ref minVal); - } - - ReadOnlySpan values = MemoryMarshal.Cast(units); - double max = double.MinValue; - int i = 0; - - if (Avx2.IsSupported && values.Length >= 4) - { - Vector256 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>(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 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>(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.Count) - { - int vCount = Vector.Count; - var vMax = new Vector(double.MinValue); - - for (; i <= values.Length - vCount; i += vCount) - { - var v = new Vector(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(ref max); - } - public static T Min(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (Unsafe.SizeOf() != sizeof(double)) - { - throw new InvalidOperationException($"Тип {typeof(T).Name} имеет неверный размер для SIMD."); - } - - if (units.IsEmpty) - { - double maxVal = double.MaxValue; - return Unsafe.As(ref maxVal); - } - - ReadOnlySpan values = MemoryMarshal.Cast(units); - double min = double.MaxValue; - int i = 0; - - if (Avx2.IsSupported && values.Length >= 4) - { - Vector256 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>(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 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>(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.Count) - { - int vCount = Vector.Count; - var vMin = new Vector(double.MaxValue); - - for (; i <= values.Length - vCount; i += vCount) - { - var v = new Vector(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(ref min); - } - - public static T Sum(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Sum(); - } - public static T Average(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Average(); - } - public static T Max(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Max(); - } - public static T Min(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Min(); - } - - public static T Sum(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return default; - if (collection is T[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); // Встроенный CopyTo работает быстрее, чем ручной foreach - return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Average(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); - if (collection is T[] array) return array.Average(); - if (collection is List list) return list.Average(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Max(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); - if (collection is T[] array) return array.Max(); - if (collection is List list) return list.Max(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Min(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); - if (collection is T[] array) return array.Min(); - if (collection is List list) return list.Min(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - - public static T Sum(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return default; - if (collection is T[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Average(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); - if (collection is T[] array) return array.Average(); - if (collection is List list) return list.Average(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Max(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); - if (collection is T[] array) return array.Max(); - if (collection is List list) return list.Max(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Min(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); - if (collection is T[] array) return array.Min(); - if (collection is List list) return list.Min(); - - T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - - public static T Sum(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return default; - - // Быстрый SIMD-путь для готовых коллекций (0 аллокаций) - if (collection is T[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - if (collection is ICollection col) return col.Sum(); - if (collection is IReadOnlyCollection 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() : default; - } - public static T Average(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.NaN.ToUnit(); - - if (collection is T[] array) return array.Average(); - if (collection is List list) return list.Average(); - if (collection is ICollection col) return col.Average(); - if (collection is IReadOnlyCollection 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(); - - double avg = sum / count; - return avg.ToUnit(); - } - public static T Max(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.MinValue.ToUnit(); - - if (collection is T[] array) return array.Max(); - if (collection is List list) return list.Max(); - if (collection is ICollection col) return col.Max(); - if (collection is IReadOnlyCollection 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() : double.MinValue.ToUnit(); - } - public static T Min(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.MaxValue.ToUnit(); - - if (collection is T[] array) return array.Min(); - if (collection is List list) return list.Min(); - if (collection is ICollection col) return col.Min(); - if (collection is IReadOnlyCollection 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() : double.MaxValue.ToUnit(); - } - - // ========================================== - // Sum Average Max Min (nullable) - // ========================================== - - public static T Sum(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return default; - - // Берем массив из пула строго под размер исходного Span - double[] sharedArray = ArrayPool.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(sharedArray, 0, validCount); - ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); - - return validStructs.Sum(); // Наш SIMD метод - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Average(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) - { - double nan = double.NaN; - return Unsafe.As(ref nan); - } - - double[] sharedArray = ArrayPool.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(ref nan); - } - - var validValues = new ReadOnlySpan(sharedArray, 0, validCount); - ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); - - return validStructs.Average(); // Наш SIMD метод - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Max(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) - { - double minVal = double.MinValue; - return Unsafe.As(ref minVal); - } - - double[] sharedArray = ArrayPool.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(ref minVal); - } - - var validValues = new ReadOnlySpan(sharedArray, 0, validCount); - ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); - - return validStructs.Max(); // Наш SIMD метод - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Min(this ReadOnlySpan units) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) - { - double maxVal = double.MaxValue; - return Unsafe.As(ref maxVal); - } - - double[] sharedArray = ArrayPool.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(ref maxVal); - } - - var validValues = new ReadOnlySpan(sharedArray, 0, validCount); - ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); - - return validStructs.Min(); // Наш SIMD метод - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - - public static T Sum(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Sum(); - } - public static T Average(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Average(); - } - public static T Max(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Max(); - } - public static T Min(this List list) - where T : struct, IMensuraUnit, IEquatable - { - return CollectionsMarshal.AsSpan(list).Min(); - } - - public static T Sum(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return default; - if (collection is T?[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Average(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); - if (collection is T?[] array) return array.Average(); - if (collection is List list) return list.Average(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Max(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); - if (collection is T?[] array) return array.Max(); - if (collection is List list) return list.Max(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static T Min(this ICollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); - if (collection is T?[] array) return array.Min(); - if (collection is List list) return list.Min(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - try - { - collection.CopyTo(sharedArray, 0); - return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - - public static T Sum(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return default; - if (collection is T?[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Average(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); - if (collection is T?[] array) return array.Average(); - if (collection is List list) return list.Sum(); // Подхватит Average() из-за правильной перегрузки - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Max(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); - if (collection is T?[] array) return array.Max(); - if (collection is List list) return list.Max(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - public static T Min(this IReadOnlyCollection collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); - if (collection is T?[] array) return array.Min(); - if (collection is List list) return list.Min(); - - T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); - int index = 0; - try - { - foreach (var item in collection) sharedArray[index++] = item; - return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); - } - finally { ArrayPool.Shared.Return(sharedArray); } - } - - public static T Sum(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return default; - - // Быстрый SIMD-путь для готовых коллекций за 0 аллокаций - if (collection is T?[] array) return array.Sum(); - if (collection is List list) return list.Sum(); - if (collection is ICollection col) return col.Sum(); - if (collection is IReadOnlyCollection 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() : default; - } - public static T Average(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.NaN.ToUnit(); - - if (collection is T?[] array) return array.Average(); - if (collection is List list) return list.Average(); - if (collection is ICollection col) return col.Average(); - if (collection is IReadOnlyCollection 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(); - - double avg = sum / count; - return avg.ToUnit(); - } - public static T Max(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.MinValue.ToUnit(); - - if (collection is T?[] array) return array.Max(); - if (collection is List list) return list.Max(); - if (collection is ICollection col) return col.Max(); - if (collection is IReadOnlyCollection 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() : double.MinValue.ToUnit(); - } - public static T Min(this IEnumerable collection) - where T : struct, IMensuraUnit, IEquatable - { - if (collection == null) return double.MaxValue.ToUnit(); - - if (collection is T?[] array) return array.Min(); - if (collection is List list) return list.Min(); - if (collection is ICollection col) return col.Min(); - if (collection is IReadOnlyCollection 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() : double.MaxValue.ToUnit(); - } - - // ========================================== - - - - // ========================================== - // CORE - // ========================================== - - internal static void Divide(double dividend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - var vectorizedDividend = new Vector(dividend); - int vectorSize = Vector.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>(ref currentSrc); - - // Векторное деление: константа делится на покомпонентные элементы массива - var divided = Vector.Divide(vectorizedDividend, vector); - - Unsafe.As>(ref currentDst) = divided; - } - - // Хвост - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = dividend / Unsafe.Add(ref srcRef, i); - } - } - internal static void Plus(ReadOnlySpan units, double summand, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - var vectorizedSummand = new Vector(summand); - int vectorSize = Vector.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>(ref currentSrc); - var added = vector + vectorizedSummand; - - Unsafe.As>(ref currentDst) = added; - } - - // Хвост - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) + summand; - } - } - internal static void Minus(ReadOnlySpan units, double subtrahend, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - var vectorizedSubtrahend = new Vector(subtrahend); - int vectorSize = Vector.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>(ref currentSrc); - var subtracted = vector - vectorizedSubtrahend; - - Unsafe.As>(ref currentDst) = subtracted; - } - - // Хвост - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) - subtrahend; - } - } - internal static void Minus(double minuend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - var vectorizedMinuend = new Vector(minuend); - int vectorSize = Vector.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>(ref currentSrc); - - // Векторное вычитание: константа уменьшается на покомпонентные элементы массива - var subtracted = vectorizedMinuend - vector; - - Unsafe.As>(ref currentDst) = subtracted; - } - - // Хвост - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = minuend - Unsafe.Add(ref srcRef, i); - } - } - - internal static void Pow(ReadOnlySpan units, int power, Span destination) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - int vectorSize = Vector.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>(ref currentSrc); - - // Векторный аналог быстрого (бинарного) возведения в степень - var result = Vector.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.One, result); - } - - // Сохраняем итоговый вектор - Unsafe.As>(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 Sqrt(ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(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>(ref currentSrc); - - // Аппаратный корень на уровне CPU - var sqrtV = Avx.Sqrt(v); - - // Выгружаем результат обратно в память назначения за 1 такт - Unsafe.As>(ref currentDst) = sqrtV; - } - } - // 2. ПУТЬ VECTOR (ARM64 / Apple Silicon / Старые CPU без AVX) - else if (Vector.IsHardwareAccelerated && len >= Vector.Count) - { - int vCount = Vector.Count; - int simdEnd = len & ~(vCount - 1); - - for (; i < simdEnd; i += vCount) - { - // Используем Span для кроссплатформенного создания вектора - var v = new Vector(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(this T[] units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Multiply(units, multiplicator, result); - return result; - } - public static T?[] Multiply(this T?[] units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Multiply(this double multiplicator, T[] units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T?[] Multiply(this double multiplicator, T?[] units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - // === ReadOnlySpan === - public static void Multiply(this ReadOnlySpan units, double multiplicator, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - var vectorizedMultiplicator = new Vector(multiplicator); - int vectorSize = Vector.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>(ref currentSrc); - var multiplied = vector * vectorizedMultiplicator; - - Unsafe.As>(ref currentDst) = multiplied; - } - - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * multiplicator; - } - } - public static void Multiply(this ReadOnlySpan units, double multiplicator, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - // Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов - ref var srcRef = ref MemoryMarshal.GetReference(units); - ref var dstRef = ref MemoryMarshal.GetReference(destination); - - int i = 0; - int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) - - // 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию - for (; i < unrollEnd; i += 4) - { - T? u0 = Unsafe.Add(ref srcRef, i); - T? u1 = Unsafe.Add(ref srcRef, i + 1); - T? u2 = Unsafe.Add(ref srcRef, i + 2); - T? u3 = Unsafe.Add(ref srcRef, i + 3); - - // Получаем ref-ссылки на ячейки назначения (zero-cost адресация) - ref var d0 = ref Unsafe.Add(ref dstRef, i); - ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); - ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); - ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); - - // Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага) - d0 = u0.HasValue ? (u0.Value.ToDouble() * multiplicator).ToUnit() : null; - d1 = u1.HasValue ? (u1.Value.ToDouble() * multiplicator).ToUnit() : null; - d2 = u2.HasValue ? (u2.Value.ToDouble() * multiplicator).ToUnit() : null; - d3 = u3.HasValue ? (u3.Value.ToDouble() * multiplicator).ToUnit() : 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() : null; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Multiply(this double multiplicator, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator, destination); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Multiply(this double multiplicator, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator, destination); - - // === List === - public static List Multiply(this List units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Multiply(CollectionsMarshal.AsSpan(units), multiplicator, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Multiply(this List units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var resultArray = new T?[count]; - ReadOnlySpan 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(); - } - } - return [.. resultArray]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List Multiply(this double multiplicator, List units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List Multiply(this double multiplicator, List units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - // === ICollection === - public static ICollection Multiply(this ICollection units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Multiply(multiplicator); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Multiply(new ReadOnlySpan(sharedArray, 0, count), multiplicator, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Multiply(this ICollection units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Multiply(multiplicator); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - srcSpan.Multiply(multiplicator, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ICollection Multiply(this double multiplicator, ICollection units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ICollection Multiply(this double multiplicator, ICollection units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - // === IReadOnlyCollection === - public static IReadOnlyCollection Multiply(this IReadOnlyCollection units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Multiply(multiplicator); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Multiply(sharedArray, multiplicator, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Multiply(this IReadOnlyCollection units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Multiply(multiplicator); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (item.Value.ToDouble() * multiplicator).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IReadOnlyCollection Multiply(this double multiplicator, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IReadOnlyCollection Multiply(this double multiplicator, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); - - // === IEnumerable + yeild === - static IEnumerable MultiplyIterator(IEnumerable units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (item.ToDouble() * multiplicator).ToUnit(); - } - static IEnumerable MultiplyNullableIterator(IEnumerable units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - foreach (T? item in units) - yield return item.HasValue - ? (item.Value.ToDouble() * multiplicator).ToUnit() : null; - } - - // === IEnumerable === - public static IEnumerable Multiply(this IEnumerable units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Multiply(multiplicator); - if (units is List list) return list.Multiply(multiplicator); - if (units is ICollection col) return col.Multiply(multiplicator); - if (units is IReadOnlyCollection roc) return roc.Multiply(multiplicator); - return MultiplyIterator(units, multiplicator); - } - public static IEnumerable Multiply(this IEnumerable units, double multiplicator) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Multiply(multiplicator); - if (units is List list) return list.Multiply(multiplicator); - if (units is ICollection col) return col.Multiply(multiplicator); - if (units is IReadOnlyCollection roc) return roc.Multiply(multiplicator); - return MultiplyNullableIterator(units, multiplicator); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Multiply(this double multiplicator, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable => Multiply(units, multiplicator); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Multiply(this double multiplicator, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable => Multiply(units, multiplicator); - - - - - // ========================================== - // === DIVIDE === - // ========================================== - - public static T[] Divide(this T[] units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Divide(units, divisor, result); - return result; - } - public static T?[] Divide(this T?[] units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - public static T[] Divide(this double dividend, T[] units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Divide(dividend, units, result); - return result; - } - public static T?[] Divide(this double dividend, T?[] units) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - - // === ReadOnlySpan === - public static void Divide(this ReadOnlySpan units, double divisor, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); - Span dstDouble = MemoryMarshal.Cast(destination); - - // Вместо деления в цикле, умножаем на обратное число (invDivisor) - double invDivisor = 1.0 / divisor; - var vectorizedInvDivisor = new Vector(invDivisor); - - int vectorSize = Vector.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>(ref currentSrc); - var multiplied = vector * vectorizedInvDivisor; - - Unsafe.As>(ref currentDst) = multiplied; - } - - // Хвост - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * invDivisor; - } - } - public static void Divide(this ReadOnlySpan units, double divisor, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.IsEmpty) return; - int len = units.Length; - if (len > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - - - // Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов - ref var srcRef = ref MemoryMarshal.GetReference(units); - ref var dstRef = ref MemoryMarshal.GetReference(destination); - - int i = 0; - int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) - double invDivisor = 1.0 / divisor; - - // 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию - for (; i < unrollEnd; i += 4) - { - T? u0 = Unsafe.Add(ref srcRef, i); - T? u1 = Unsafe.Add(ref srcRef, i + 1); - T? u2 = Unsafe.Add(ref srcRef, i + 2); - T? u3 = Unsafe.Add(ref srcRef, i + 3); - - // Получаем ref-ссылки на ячейки назначения (zero-cost адресация) - ref var d0 = ref Unsafe.Add(ref dstRef, i); - ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); - ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); - ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); - - // Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага) - d0 = u0.HasValue ? (u0.Value.ToDouble() / invDivisor).ToUnit() : null; - d1 = u1.HasValue ? (u1.Value.ToDouble() / invDivisor).ToUnit() : null; - d2 = u2.HasValue ? (u2.Value.ToDouble() / invDivisor).ToUnit() : null; - d3 = u3.HasValue ? (u3.Value.ToDouble() / invDivisor).ToUnit() : 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() : null; - } - } - public static void Divide(this double dividend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.Length > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - Extensions.Divide(dividend, units, destination); - } - public static void Divide(this double dividend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - 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() : null; - d1 = u1.HasValue ? (dividend / u1.Value.ToDouble()).ToUnit() : null; - d2 = u2.HasValue ? (dividend / u2.Value.ToDouble()).ToUnit() : null; - d3 = u3.HasValue ? (dividend / u3.Value.ToDouble()).ToUnit() : 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() : null; - } - } - - // === List === - public static List Divide(this List units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Divide(CollectionsMarshal.AsSpan(units), divisor, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Divide(this List units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - 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 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(); - } - } - return [.. resultArray]; - } - public static List Divide(this double dividend, List units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Divide(dividend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Divide(this double dividend, List units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var resultArray = new T?[count]; - ReadOnlySpan 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(); - } - } - return [.. resultArray]; - } - - // === ICollection === - public static ICollection Divide(this ICollection units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Divide(divisor); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Divide(new ReadOnlySpan(sharedArray, 0, count), divisor, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Divide(this ICollection units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Divide(divisor); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - srcSpan.Divide(divisor, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - public static ICollection Divide(this double dividend, ICollection units) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return dividend.Divide(list); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Divide(dividend, new ReadOnlySpan(sharedArray, 0, count), finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Divide(this double dividend, ICollection units) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return dividend.Divide(list); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - dividend.Divide(srcSpan, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IReadOnlyCollection === - public static IReadOnlyCollection Divide(this IReadOnlyCollection units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T[] array) return array.Multiply(divisor); - if (units is List list) return list.Multiply(divisor); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Divide(sharedArray, divisor, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Divide(this IReadOnlyCollection units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T?[] array) return array.Multiply(divisor); - if (units is List list) return list.Multiply(divisor); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - double invDivisor = 1.0 / divisor; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (item.Value.ToDouble() / invDivisor).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - public static IReadOnlyCollection Divide(this double dividend, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T[] array) return array.Multiply(dividend); - if (units is List list) return list.Multiply(dividend); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Divide(dividend, sharedArray, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Divide(this double dividend, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T?[] array) return array.Multiply(dividend); - if (units is List list) return list.Multiply(dividend); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (dividend / item.Value.ToDouble()).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IEnumerable + yeild === - static IEnumerable DivideIterator(IEnumerable units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - double invDivisor = 1.0 / divisor; - foreach (var item in units) - yield return (item.ToDouble() / invDivisor).ToUnit(); - } - static IEnumerable DivideNullableIterator(IEnumerable units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - double invDivisor = 1.0 / divisor; - foreach (T? item in units) - yield return item.HasValue - ? (item.Value.ToDouble() / invDivisor).ToUnit() : null; - } - static IEnumerable DivideIterator(double dividend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (dividend / item.ToDouble()).ToUnit(); - } - static IEnumerable DivideNullableIterator(double dividend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - foreach (T? item in units) - yield return item.HasValue - ? (dividend / item.Value.ToDouble()).ToUnit() : null; - } - - // === IEnumerable === - public static IEnumerable Divide(this IEnumerable units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Divide(divisor); - if (units is List list) return list.Divide(divisor); - if (units is ICollection col) return col.Divide(divisor); - if (units is IReadOnlyCollection roc) return roc.Divide(divisor); - return DivideIterator(units, divisor); - } - public static IEnumerable Divide(this IEnumerable units, double divisor) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Divide(divisor); - if (units is List list) return list.Divide(divisor); - if (units is ICollection col) return col.Divide(divisor); - if (units is IReadOnlyCollection roc) return roc.Divide(divisor); - return DivideNullableIterator(units, divisor); - } - public static IEnumerable Divide(this double dividend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return dividend.Divide(array); - if (units is List list) return dividend.Divide(list); - if (units is ICollection col) return dividend.Divide(col); - if (units is IReadOnlyCollection roc) return dividend.Divide(roc); - return DivideIterator(dividend, units); - } - public static IEnumerable Divide(this double dividend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return dividend.Divide(array); - if (units is List list) return dividend.Divide(list); - if (units is ICollection col) return dividend.Divide(col); - if (units is IReadOnlyCollection roc) return dividend.Divide(roc); - return DivideNullableIterator(dividend, units); - } - - - - - // ========================================== - // === PLUS === - // ========================================== - - public static T[] Plus(this T[] units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Plus(units, summand, result); - return result; - } - public static T?[] Plus(this T?[] units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] Plus(this double summand, T[] units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T?[] Plus(this double summand, T?[] units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - // === ReadOnlySpan === - public static void Plus(this ReadOnlySpan units, double summand, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.Length > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - Extensions.Plus(units, summand, destination); - } - public static void Plus(this ReadOnlySpan units, double summand, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - 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() : null; - d1 = u1.HasValue ? (u1.Value.ToDouble() + summand).ToUnit() : null; - d2 = u2.HasValue ? (u2.Value.ToDouble() + summand).ToUnit() : null; - d3 = u3.HasValue ? (u3.Value.ToDouble() + summand).ToUnit() : 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() : null; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Plus(this double summand, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand, destination); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Plus(this double summand, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand, destination); - - // === List === - public static List Plus(this List units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Plus(CollectionsMarshal.AsSpan(units), summand, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Plus(this List units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var resultArray = new T?[count]; - ReadOnlySpan 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(); - } - } - return [.. resultArray]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List Plus(this double summand, List units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List Plus(this double summand, List units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - // === ICollection === - public static ICollection Plus(this ICollection units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Plus(summand); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Plus(new ReadOnlySpan(sharedArray, 0, count), summand, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Plus(this ICollection units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Plus(summand); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - srcSpan.Plus(summand, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ICollection Plus(this double summand, ICollection units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ICollection Plus(this double summand, ICollection units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - // === IReadOnlyCollection === - public static IReadOnlyCollection Plus(this IReadOnlyCollection units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Plus(summand); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Plus(sharedArray, summand, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Plus(this IReadOnlyCollection units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Plus(summand); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (item.Value.ToDouble() + summand).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IReadOnlyCollection Plus(this double summand, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IReadOnlyCollection Plus(this double summand, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); - - // === IEnumerable + yeild === - static IEnumerable PlusIterator(IEnumerable units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (item.ToDouble() + summand).ToUnit(); - } - static IEnumerable PlusNullableIterator(IEnumerable units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - foreach (T? item in units) - yield return item.HasValue - ? (item.Value.ToDouble() + summand).ToUnit() : null; - } - - // === IEnumerable === - public static IEnumerable Plus(this IEnumerable units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Plus(summand); - if (units is List list) return list.Plus(summand); - if (units is ICollection col) return col.Plus(summand); - if (units is IReadOnlyCollection roc) return roc.Plus(summand); - return PlusIterator(units, summand); - } - public static IEnumerable Plus(this IEnumerable units, double summand) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Plus(summand); - if (units is List list) return list.Plus(summand); - if (units is ICollection col) return col.Plus(summand); - if (units is IReadOnlyCollection roc) return roc.Plus(summand); - return PlusNullableIterator(units, summand); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Plus(this double summand, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable => Plus(units, summand); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Plus(this double summand, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable => Plus(units, summand); - - - - - // ========================================== - // === MINUS === - // ========================================== - - public static T[] Minus(this T[] units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Minus(units, subtrahend, result); - return result; - } - public static T?[] Minus(this T?[] units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - public static T[] Minus(this double minuend, T[] units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new T[len]; - Minus(minuend, units, result); - return result; - } - public static T?[] Minus(this double minuend, T?[] units) - where T : struct, IMensuraUnit, IEquatable - { - 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(); - } - } - return result; - } - - // === ReadOnlySpan === - public static void Minus(this ReadOnlySpan units, double subtrahend, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.Length > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - Extensions.Minus(units, subtrahend, destination); - } - public static void Minus(this ReadOnlySpan units, double subtrahend, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - 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() : null; - d1 = u1.HasValue ? (u1.Value.ToDouble() - subtrahend).ToUnit() : null; - d2 = u2.HasValue ? (u2.Value.ToDouble() - subtrahend).ToUnit() : null; - d3 = u3.HasValue ? (u3.Value.ToDouble() - subtrahend).ToUnit() : 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() : null; - } - } - public static void Minus(this double minuend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - if (units.Length > destination.Length) - throw new ArgumentException("Целевой буфер destination меньше исходного source."); - Extensions.Minus(minuend, units, destination); - } - public static void Minus(this double minuend, ReadOnlySpan units, Span destination) - where T : struct, IMensuraUnit, IEquatable - { - 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() : null; - d1 = u1.HasValue ? (minuend - u1.Value.ToDouble()).ToUnit() : null; - d2 = u2.HasValue ? (minuend - u2.Value.ToDouble()).ToUnit() : null; - d3 = u3.HasValue ? (minuend - u3.Value.ToDouble()).ToUnit() : 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() : null; - } - } - - // === List === - public static List Minus(this List units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Minus(CollectionsMarshal.AsSpan(units), subtrahend, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Minus(this List units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var resultArray = new T?[count]; - ReadOnlySpan 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(); - } - } - return [.. resultArray]; - } - public static List Minus(this double minuend, List units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Minus(minuend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Minus(this double minuend, List units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var resultArray = new T?[count]; - ReadOnlySpan 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(); - } - } - return [.. resultArray]; - } - - // === ICollection === - public static ICollection Minus(this ICollection units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Minus(subtrahend); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Minus(new ReadOnlySpan(sharedArray, 0, count), subtrahend, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Minus(this ICollection units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return list.Minus(subtrahend); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - srcSpan.Minus(subtrahend, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - public static ICollection Minus(this double minuend, ICollection units) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return minuend.Minus(list); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - units.CopyTo(sharedArray, 0); - Minus(minuend, new ReadOnlySpan(sharedArray, 0, count), finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static ICollection Minus(this double minuend, ICollection units) - where T : struct, IMensuraUnit, IEquatable - { - 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 list) return minuend.Minus(list); - - var resultArray = new T?[count]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - try - { - units.CopyTo(sharedNullableArray, 0); - var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); - minuend.Minus(srcSpan, resultArray.AsSpan()); - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IReadOnlyCollection === - public static IReadOnlyCollection Minus(this IReadOnlyCollection units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T[] array) return array.Multiply(subtrahend); - if (units is List list) return list.Multiply(subtrahend); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Minus(sharedArray, subtrahend, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Minus(this IReadOnlyCollection units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T?[] array) return array.Multiply(subtrahend); - if (units is List list) return list.Multiply(subtrahend); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (item.Value.ToDouble() - subtrahend).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - public static IReadOnlyCollection Minus(this double minuend, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T[] array) return array.Multiply(minuend); - if (units is List list) return list.Multiply(minuend); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new T[count]; - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Minus(minuend, sharedArray, finalResult.AsSpan()); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - public static IReadOnlyCollection Minus(this double minuend, IReadOnlyCollection units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T?[] array) return array.Multiply(minuend); - if (units is List list) return list.Multiply(minuend); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); - var resultArray = new T?[count]; - try - { - int index = 0; - foreach (var item in units) sharedNullableArray[index++] = item; - for (int i = 0; i < count; i++) - { - T? item = sharedNullableArray[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = (minuend - item.Value.ToDouble()).ToUnit(); - } - } - return resultArray; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IEnumerable + yeild === - static IEnumerable MinusIterator(IEnumerable units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (item.ToDouble() - subtrahend).ToUnit(); - } - static IEnumerable MinusNullableIterator(IEnumerable units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - foreach (T? item in units) - yield return item.HasValue - ? (item.Value.ToDouble() - subtrahend).ToUnit() : null; - } - static IEnumerable MinusIterator(double minuend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (minuend - item.ToDouble()).ToUnit(); - } - static IEnumerable MinusNullableIterator(double minuend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - foreach (T? item in units) - yield return item.HasValue - ? (minuend - item.Value.ToDouble()).ToUnit() : null; - } - - // === IEnumerable === - public static IEnumerable Minus(this IEnumerable units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Minus(subtrahend); - if (units is List list) return list.Minus(subtrahend); - if (units is ICollection col) return col.Minus(subtrahend); - if (units is IReadOnlyCollection roc) return roc.Minus(subtrahend); - return MinusIterator(units, subtrahend); - } - public static IEnumerable Minus(this IEnumerable units, double subtrahend) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Minus(subtrahend); - if (units is List list) return list.Minus(subtrahend); - if (units is ICollection col) return col.Minus(subtrahend); - if (units is IReadOnlyCollection roc) return roc.Minus(subtrahend); - return MinusNullableIterator(units, subtrahend); - } - public static IEnumerable Minus(this double minuend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return minuend.Minus(array); - if (units is List list) return minuend.Minus(list); - if (units is ICollection col) return minuend.Minus(col); - if (units is IReadOnlyCollection roc) return minuend.Minus(roc); - return MinusIterator(minuend, units); - } - public static IEnumerable Minus(this double minuend, IEnumerable units) - where T : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return minuend.Minus(array); - if (units is List list) return minuend.Minus(list); - if (units is ICollection col) return minuend.Minus(col); - if (units is IReadOnlyCollection roc) return minuend.Minus(roc); - return MinusNullableIterator(minuend, units); - } - - - - - // ========================================== - // === ЦЕЛАЯ СТЕПЕНЬ === - // ========================================== - - internal static R[] Pow(this T[] units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new R[len]; - Extensions.Pow(units, power, result); - return result; - } - internal static R?[] Pow(this T?[] units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var result = new R?[len]; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - item = item.Value.ToDouble().QuickPow(power).ToUnit(); - } - return result; - } - - // === ReadOnlySpan === - internal static Span Pow(this ReadOnlySpan units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units.Length == 0) return []; - var result = new R[units.Length]; - Extensions.Pow(units, power, result); - return result; - } - internal static Span Pow(this ReadOnlySpan units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - int len = units.Length; - if (len == 0) return []; - var res = new T?[len]; - units.CopyTo(res); - var result = Unsafe.As(ref res); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = item.Value.ToDouble().QuickPow(power).ToUnit(); - } - return result; - } - - // === List === - internal static List Pow(this List units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - - var result = new List(count); - result.SetCountUnsafe(count); - Extensions.Pow(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result)); - return result; - } - internal static List Pow(this List units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var resultArray = new R?[len]; - var srcSpan = CollectionsMarshal.AsSpan(units); - for (int i = 0; i < len; i++) - { - T? item = srcSpan[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = item.Value.ToDouble().QuickPow(power).ToUnit(); - } - } - return [.. resultArray]; - } - - // === ICollection === - internal static ICollection Pow(this ICollection units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - ICollection result = []; - foreach (var item in units) - result.Add(item.ToDouble().QuickPow(power).ToUnit()); - return result; - } - internal static ICollection Pow(this ICollection units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - ICollection result = []; - foreach (var item in units) - result.Add((item.HasValue ? item.Value.ToDouble().QuickPow(power) : 0d).ToUnit()); - return result; - } - - // === IReadOnlyCollection === - internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - T[] sharedArray = ArrayPool.Shared.Rent(count); - var finalResult = new R[count]; - - try - { - int index = 0; - foreach (var item in units) sharedArray[index++] = item; - Pow(sharedArray, power, finalResult); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int count = units.Count; - if (count == 0) return []; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - // Сразу выделяем итоговый массив. Никаких ArrayPool и try-finally! - var result = new R?[units.Count]; - int i = 0; - int absPower = Math.Abs(power); - - foreach (T? item in units) - { - if (item.HasValue) - { - double @base = item.Value.ToDouble(); - double res = 1.0; - int currentPower = absPower; - - // Быстрое бинарное возведение в степень на лету - while (currentPower > 0) - { - if ((currentPower & 1) == 1) res *= @base; - @base *= @base; - currentPower >>= 1; - } - - // Прямая ref-запись в итоговый массив за 1 проход - ref var dst = ref result[i]; - dst = (power < 0 ? 1.0 / res : res).ToUnit(); - } - i++; - } - - return result; - } - - // === IEnumerable + yeild === - static IEnumerable PowIterator(IEnumerable units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return (item.ToDouble().QuickPow(power)).ToUnit(); - } - static IEnumerable PowNullableIterator(IEnumerable units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return item.HasValue - ? item.Value.ToDouble().QuickPow(power).ToUnit() : null; - } - - // === IEnumerable === - internal static IEnumerable Pow(this IEnumerable units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - if (units is ICollection col) return col.Pow(power); - if (units is IReadOnlyCollection roc) return roc.Pow(power); - else return PowIterator(units, power); - } - internal static IEnumerable Pow(this IEnumerable units, int power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - if (units is ICollection col) return col.Pow(power); - if (units is IReadOnlyCollection roc) return roc.Pow(power); - else return PowNullableIterator(units, power); - } - - - - - // ========================================== - // === ДРОБНАЯ СТЕПЕНЬ === - // ========================================== - - internal static R[] Pow(this T[] units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Length; - if (len == 0) return []; - - var finalResult = new R[len]; - if (power != 0.5) - { - for (int i = 0; i < len; i++) - { - ref var dst = ref finalResult[i]; - dst = Math.Pow(units[i].ToDouble(), power).ToUnit(); - } - } - else Sqrt(MemoryMarshal.Cast(units), finalResult); - return finalResult; - } - internal static R?[] Pow(this T?[] units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (R?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - item = Math.Pow(item.Value.ToDouble(), power).ToUnit(); - } - } - return result; - } - - // === ReadOnlySpan === - internal static void Pow(this ReadOnlySpan source, double power, Span destination) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (source.IsEmpty) return; - - int len = source.Length; - ReadOnlySpan srcDouble = MemoryMarshal.Cast(source); - Span dstDouble = MemoryMarshal.Cast(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>(ref currentSrc); - - // Аппаратный корень через инструкцию vsqrtpd - var sqrtV = Avx.Sqrt(v); - - // Совместимый с .NET 6 способ записи вектора в ref double - ref double currentDst = ref Unsafe.Add(ref dstRef, i); - Unsafe.As>(ref currentDst) = sqrtV; - } - } - - // Хвост для корня - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i)); - } - return; - } - - // 3. ОБЩИЙ СЛУЧАЙ ДЛЯ СЛОЖНЫХ СТЕПЕНЕЙ - // Из-за ограничений .NET 6 здесь мы разворачиваем цикл вручную на 4 элемента, - // но делаем это через ref-ссылки без использования fixed-указателей - if (len >= 4) - { - int simdEnd = len & ~3; - for (; i < simdEnd; i += 4) - { - ref double sRef = ref Unsafe.Add(ref srcRef, i); - ref double dRef = ref Unsafe.Add(ref dstRef, i); - - // JIT попытается это автовекторизовать, но даже без нее - // этот код работает быстрее fixed за счет отсутствия пиннинга памяти GC - Unsafe.Add(ref dRef, 0) = Math.Pow(Unsafe.Add(ref sRef, 0), power); - Unsafe.Add(ref dRef, 1) = Math.Pow(Unsafe.Add(ref sRef, 1), power); - Unsafe.Add(ref dRef, 2) = Math.Pow(Unsafe.Add(ref sRef, 2), power); - Unsafe.Add(ref dRef, 3) = Math.Pow(Unsafe.Add(ref sRef, 3), power); - } - } - - // Хвост массива - for (; i < len; i++) - { - Unsafe.Add(ref dstRef, i) = Math.Pow(Unsafe.Add(ref srcRef, i), power); - } - } - internal static void Pow(this ReadOnlySpan units, double power, Span destination) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - int len = units.Length; - if (len == 0) return; - - for (int i = 0; i < len; i++) - { - T? item = units[i]; - if (item.HasValue) - { - // Прямая ref-запись в переданный снаружи destination - ref var dst = ref destination[i]; - dst = Math.Pow(item.Value.ToDouble(), power).ToUnit(); - } - } - } - - // === List === - internal static List Pow(this List units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var resultArray = new R[len]; - var srcSpan = CollectionsMarshal.AsSpan(units); - if (power == 0.5) - { - ReadOnlySpan srcAsR = MemoryMarshal.Cast(srcSpan); - Sqrt(srcAsR, resultArray.AsSpan()); - } - else - { - Span dstSpan = resultArray.AsSpan(); - for (int i = 0; i < len; i++) - { - ref var dst = ref dstSpan[i]; - dst = Math.Pow(srcSpan[i].ToDouble(), power).ToUnit(); - } - } - return [.. resultArray]; - } - internal static List Pow(this List units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var resultArray = new R?[len]; - var srcSpan = CollectionsMarshal.AsSpan(units); - for (int i = 0; i < len; i++) - { - T? item = srcSpan[i]; - if (item.HasValue) - { - ref var dst = ref resultArray[i]; - dst = Math.Pow(item.Value.ToDouble(), power).ToUnit(); - } - } - return [.. resultArray]; - } - - // === ICollection === - internal static ICollection Pow(this ICollection units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - T[] sharedArray = ArrayPool.Shared.Rent(len); - var finalResult = new R[len]; - try - { - units.CopyTo(sharedArray, 0); - if (power == 0.5) - { - Sqrt(MemoryMarshal.Cast(sharedArray), finalResult); - } - else - { - for (int i = 0; i < len; i++) - finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit(); - } - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - internal static ICollection Pow(this ICollection units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - T?[] sharedNullableArray = ArrayPool.Shared.Rent(len); - var finalResult = new R?[len]; - try - { - units.CopyTo(sharedNullableArray, 0); - for (int i = 0; i < len; i++) - { - T? item = sharedNullableArray[i]; - finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : default; - } - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IReadOnlyCollection === - internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - var finalResult = new R[len]; - var sharedArray = ArrayPool.Shared.Rent(len); - try - { - if (units is ICollection collection) - { - collection.CopyTo(sharedArray, 0); - } - else - { - int idx = 0; - foreach (var item in units) sharedArray[idx++] = item; - } - - if (power != 0.5) - { - for (int i = 0; i < len; i++) - finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit(); - } - else Sqrt(MemoryMarshal.Cast(sharedArray), finalResult); - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedArray); - } - } - internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - - var finalResult = new R?[len]; - T?[] sharedNullableArray = ArrayPool.Shared.Rent(len); - - try - { - if (units is ICollection collection) - { - collection.CopyTo(sharedNullableArray, 0); - } - else - { - int idx = 0; - foreach (T? item in units) sharedNullableArray[idx++] = item; - } - - for (int i = 0; i < len; i++) - { - T? item = sharedNullableArray[i]; - finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : default; - } - - return finalResult; - } - finally - { - ArrayPool.Shared.Return(sharedNullableArray); - } - } - - // === IEnumerable + yield === - internal static IEnumerable PowIterator(this IEnumerable units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return Math.Pow(item.ToDouble(), power).ToUnit(); - } - internal static IEnumerable PowNullableIterator(this IEnumerable units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - foreach (var item in units) - yield return item.HasValue - ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : null; - } - - // === IEnumerable === - internal static IEnumerable Pow(this IEnumerable units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - if (units is ICollection col) return col.Pow(power); - if (units is IReadOnlyCollection roc) return roc.Pow(power); - else return PowIterator(units, power); - } - internal static IEnumerable Pow(this IEnumerable units, double power) - where T : struct, IMensuraUnit, IEquatable - where R : struct, IMensuraUnit, IEquatable - { - if (units is null) return null!; - if (units is T?[] array) return array.Pow(power); - if (units is List list) return list.Pow(power); - if (units is ICollection col) return col.Pow(power); - if (units is IReadOnlyCollection roc) return roc.Pow(power); - else return PowNullableIterator(units, power); - } - - - - - - - - - - - - - - - //internal static U Protect(this U? metric) where U : class, IMetric, new() => metric ?? new(); - //internal static C Protect(this C? collection) - // where C : IMetricCollection, new() where U : class, IMetric, new() => collection ?? new(); - - - //public static C MetricSelect(this C? collection, Func? selector) - // where C : IMetricCollection, new() where U : class, IMetric, 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 MetricSelect(this IMetricCollection collection, Func selector) - // where U : class, IMetric, 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 MetricSelect(this IMetricCollection? collection, Func? selector) - // where Ux : class, IMetric, new() where Uz : class, IMetric, new() - //{ - // if (collection is not null && selector is not null) - // { - // var destCollection = collection.CreateByInstance(collection.Count); - // for (int i = 0; i < collection.Count; i++) - // destCollection[i] = selector(collection[i]); - // return destCollection; - // } - // return null!; - //} - //public static MetricCollection MetricSelect(this MetricCollection? collection, Func? selector) - // where Ux : class, IMetric, new() where Uz : class, IMetric, new() - //{ - // if (collection is not null && selector is not null) - // { - // var destCollection = collection.CreateByInstance(collection.Count()); - // for (int i = 0; i < collection.Count(); i++) - // destCollection[i] = selector(collection[i]); - // return destCollection; - // } - // return null!; - //} - //public static MetricArray MetricSelect(this MetricArray? collection, Func? selector) - // where Ux : class, IMetric, new() where Uz : class, IMetric, new() - //{ - // var coll = collection?.ToArray(); - // if (coll is not null && selector is not null) - // { - // var destCollection = new MetricArray(coll.Length); - // for (int i = 0; i < coll.Length; i++) - // destCollection[i] = selector(coll[i]); - // return destCollection; - // } - // return null!; - //} - //public static MetricList MetricSelect(this MetricList? collection, Func? selector) - // where Ux : class, IMetric, new() where Uz : class, IMetric, new() - //{ - // if (collection is not null && selector is not null) - // { - // var destCollection = new MetricList(collection.Count); - // for (int i = 0; i < collection.Count; i++) - // destCollection[i] = selector(collection[i]); - // return destCollection; - // } - // return null!; - //} - //public static MetricObservableCollection MetricSelect(this MetricObservableCollection? collection, Func? selector) - // where Ux : class, IMetric, new() where Uz : class, IMetric, new() - //{ - // if (collection is not null && selector is not null) - // { - // var destCollection = new MetricObservableCollection(collection.Count); - // for (int i = 0; i < collection.Count; i++) - // destCollection[i] = selector(collection[i]); - // return destCollection; - // } - // return null!; - //} - - - //public static double[] MetricSelect(this MetricArray collection, Func selector) - // where U : class, IMetric, 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 MetricSelect(this MetricList collection, Func selector) - // where U : class, IMetric, new() - //{ - // var coll = collection ?? []; - // var list = new List(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 MetricSelect(this MetricObservableCollection collection, Func selector) - // where U : class, IMetric, new() - //{ - // var coll = collection ?? []; - // var list = new List(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(this C? collection, Func? Set) - // where C : IMetricCollection, new() where U : class, IMetric, 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(this U? T1, U? T2) where U : class, IMetric, new() => (T1.Protect_Value() < T2.Protect_Value() ? T1 : T2).Protect(); - //public static U Min(this U T1, IEnumerable units) where U : class, IMetric, new() => (T1 ?? new()).Min((units ?? []).MaxBy(u => u.Protect_Value())); - - //public static U Max(this U? T1, U? T2) where U : class, IMetric, new() => (T1.Protect_Value() > T2.Protect_Value() ? T1 : T2).Protect(); - //public static U Max(this U T1, IEnumerable units) where U : class, IMetric, 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(this N number) where N : INumber => Convert.ToDouble(number); - ////internal static double ToDouble(this N? number) where N : struct, INumber => number is not null ? Convert.ToDouble(number) : 0d; - - - //internal static IEnumerable MetricSelect(this double[] nums, Func selector) where U : class, IMetric, new() => nums.Select(selector); - //internal static IEnumerable MetricSelect(this double?[] nums, Func selector) where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - //internal static IEnumerable MetricSelect(this N[] nums, Func selector) where N : INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - //internal static IEnumerable MetricSelect(this N?[] nums, Func selector) where N : struct, INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - - - //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where U : class, IMetric, new() => nums.Select(selector); - //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where N : INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where N : struct, INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); - - - - - //public static U Clone(this U? metric) where U : class, IMetric, new() => new() { _Value = metric.Protect_Value() }; - //public static U Abs(this U? metric) where U : class, IMetric, new() => new() { _Value = Math.Abs(metric.Protect_Value()) }; - - ///// C^2 = A^2 + B^2 - ///// C = (A^2 + B^2).Sqrt(2) - //public static U Hypotenuse(this U? A, U? B) where U : class, IMetric, new() - //{ - // var a = A.Protect_Value(); - // var b = B.Protect_Value(); - // return new U() { _Value = Math.Sqrt(a * a + b * b) }; - //} - ///// C^2 = A^2 + B^2 - ///// B = (C^2 - A^2).Sqrt(2) - //public static U KatetFromHyp(this U? A, U? C) where U : class, IMetric, new() - //{ - // var a = A.Protect_Value(); - // var c = C.Protect_Value(); - // return new U() { _Value = Math.Sqrt(c * c - a * a) }; - //} - ///// C^2 = A^2 + B^2 - ///// B = (C^2 - A^2).Sqrt(2) - //public static U KatetFromKatet(this U? C, U? A) where U : class, IMetric, 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(this IEnumerable args) where U : IMetric, new() => new() { _Value = args?.Where(t => t is not null).Sum(m => m.Protect_Value()) ?? 0d }; - //public static U MetricAverage(this IEnumerable args) where U : IMetric, new() => new() { _Value = args?.Average(m => m.Protect_Value()) ?? double.NaN }; - //public static U MetricMax(this IEnumerable args) where U : IMetric, new() => new() { _Value = args.Max(m => m.Protect_Value()) }; - //public static U MetricMin(this IEnumerable args) where U : IMetric, new() => new() { _Value = args.Min(m => m.Protect_Value()) }; - - - - //public static C MetricSum(this IEnumerable> collections) - // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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(this IEnumerable> collections) - // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, new() - // => collections.Sum() / collections.Count(); - - - - //public static U MetricSumBy(this IEnumerable source, Func selector) - // where U : class, IMetric, 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(this IEnumerable source, Func selector) - // where U : class, IMetric, 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(this IEnumerable source, Func> selector) - // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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(this IEnumerable source, Func> selector) - // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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 ToMetricArray(this IEnumerable source) where U : class, IMetric, new() => new(source); - //public static MetricList ToMetricList(this IEnumerable source) where U : class, IMetric, new() => new(source); -} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs b/QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs new file mode 100644 index 0000000..1272348 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/AggregateUnitExtensions.cs @@ -0,0 +1,955 @@ +using System.Buffers; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class AggregateUnitExtensions +{ + // Sum Average Max Min (не nullable) ========================================== + + + // === ReadOnlySpan === SIMD + public static T Sum(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return default; + + ReadOnlySpan values = MemoryMarshal.Cast(units); + double sum = 0; + int i = 0; + + // 1. ПУТЬ AVX2 (Intel/AMD) — Самый быстрый, обрабатывает по 4 элемента через Unsafe + if (Avx2.IsSupported && values.Length >= 4) + { + Vector256 vSum = Vector256.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>(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 vSum = Vector256.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>(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.Count) + { + int vCount = Vector.Count; + Vector vSum = Vector.Zero; + + for (; i <= values.Length - vCount; i += vCount) + { + var v = new Vector(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(ref sum); + } + public static T Average(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + // Если коллекция пустая, возвращаем структуру, содержащую double.NaN + if (units.IsEmpty) + { + double nan = double.NaN; + return Unsafe.As(ref nan); + } + + ReadOnlySpan values = MemoryMarshal.Cast(units); + double sum = 0; + int i = 0; + + // 1. ПУТЬ AVX2 (Intel/AMD) + if (Avx2.IsSupported && values.Length >= 4) + { + Vector256 vSum = Vector256.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>(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 vSum = Vector256.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>(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.Count) + { + int vCount = Vector.Count; + Vector vSum = Vector.Zero; + + for (; i <= values.Length - vCount; i += vCount) + { + var v = new Vector(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(ref average); + } + public static T Max(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) + { + double minVal = double.MinValue; + return Unsafe.As(ref minVal); + } + + ReadOnlySpan values = MemoryMarshal.Cast(units); + double max = double.MinValue; + int i = 0; + + if (Avx2.IsSupported && values.Length >= 4) + { + Vector256 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>(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 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>(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.Count) + { + int vCount = Vector.Count; + var vMax = new Vector(double.MinValue); + + for (; i <= values.Length - vCount; i += vCount) + { + var v = new Vector(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(ref max); + } + public static T Min(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) + { + double maxVal = double.MaxValue; + return Unsafe.As(ref maxVal); + } + + ReadOnlySpan values = MemoryMarshal.Cast(units); + double min = double.MaxValue; + int i = 0; + + if (Avx2.IsSupported && values.Length >= 4) + { + Vector256 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>(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 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>(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.Count) + { + int vCount = Vector.Count; + var vMin = new Vector(double.MaxValue); + + for (; i <= values.Length - vCount; i += vCount) + { + var v = new Vector(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(ref min); + } + + // === List === + public static T Sum(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Sum(); + } + public static T Average(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Average(); + } + public static T Max(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Max(); + } + public static T Min(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Min(); + } + + // === ICollection === + public static T Sum(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return default; + if (collection is T[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); // Встроенный CopyTo работает быстрее, чем ручной foreach + return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Average(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); + if (collection is T[] array) return array.Average(); + if (collection is List list) return list.Average(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Max(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); + if (collection is T[] array) return array.Max(); + if (collection is List list) return list.Max(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Min(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); + if (collection is T[] array) return array.Min(); + if (collection is List list) return list.Min(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + + // === IReadOnlyCollection === + public static T Sum(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return default; + if (collection is T[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Average(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); + if (collection is T[] array) return array.Average(); + if (collection is List list) return list.Average(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Max(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); + if (collection is T[] array) return array.Max(); + if (collection is List list) return list.Max(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Min(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); + if (collection is T[] array) return array.Min(); + if (collection is List list) return list.Min(); + + T[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + + + // === IEnumerable === + public static T Sum(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return default; + + // Быстрый SIMD-путь для готовых коллекций (0 аллокаций) + if (collection is T[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + if (collection is ICollection col) return col.Sum(); + if (collection is IReadOnlyCollection 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() : default; + } + public static T Average(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.NaN.ToUnit(); + + if (collection is T[] array) return array.Average(); + if (collection is List list) return list.Average(); + if (collection is ICollection col) return col.Average(); + if (collection is IReadOnlyCollection 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(); + + double avg = sum / count; + return avg.ToUnit(); + } + public static T Max(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.MinValue.ToUnit(); + + if (collection is T[] array) return array.Max(); + if (collection is List list) return list.Max(); + if (collection is ICollection col) return col.Max(); + if (collection is IReadOnlyCollection 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() : double.MinValue.ToUnit(); + } + public static T Min(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.MaxValue.ToUnit(); + + if (collection is T[] array) return array.Min(); + if (collection is List list) return list.Min(); + if (collection is ICollection col) return col.Min(); + if (collection is IReadOnlyCollection 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() : double.MaxValue.ToUnit(); + } + + + + // Sum Average Max Min (nullable) ========================================== + + + // === ReadOnlySpan === + public static T Sum(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return default; + + // Берем массив из пула строго под размер исходного Span + double[] sharedArray = ArrayPool.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(sharedArray, 0, validCount); + ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); + + return validStructs.Sum(); // Наш SIMD метод + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Average(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) + { + double nan = double.NaN; + return Unsafe.As(ref nan); + } + + double[] sharedArray = ArrayPool.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(ref nan); + } + + var validValues = new ReadOnlySpan(sharedArray, 0, validCount); + ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); + + return validStructs.Average(); // Наш SIMD метод + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Max(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) + { + double minVal = double.MinValue; + return Unsafe.As(ref minVal); + } + + double[] sharedArray = ArrayPool.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(ref minVal); + } + + var validValues = new ReadOnlySpan(sharedArray, 0, validCount); + ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); + + return validStructs.Max(); // Наш SIMD метод + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Min(this ReadOnlySpan units) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) + { + double maxVal = double.MaxValue; + return Unsafe.As(ref maxVal); + } + + double[] sharedArray = ArrayPool.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(ref maxVal); + } + + var validValues = new ReadOnlySpan(sharedArray, 0, validCount); + ReadOnlySpan validStructs = MemoryMarshal.Cast(validValues); + + return validStructs.Min(); // Наш SIMD метод + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + + // === List === + public static T Sum(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Sum(); + } + public static T Average(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Average(); + } + public static T Max(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Max(); + } + public static T Min(this List list) + where T : struct, IMensuraUnit, IEquatable + { + return CollectionsMarshal.AsSpan(list).Min(); + } + + // === ICollection === + public static T Sum(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return default; + if (collection is T?[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Average(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); + if (collection is T?[] array) return array.Average(); + if (collection is List list) return list.Average(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Max(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); + if (collection is T?[] array) return array.Max(); + if (collection is List list) return list.Max(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static T Min(this ICollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); + if (collection is T?[] array) return array.Min(); + if (collection is List list) return list.Min(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + try + { + collection.CopyTo(sharedArray, 0); + return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + + // === IReadOnlyCollection === + public static T Sum(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return default; + if (collection is T?[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Sum(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Average(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.NaN.ToUnit(); + if (collection is T?[] array) return array.Average(); + if (collection is List list) return list.Average(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Average(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Max(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MinValue.ToUnit(); + if (collection is T?[] array) return array.Max(); + if (collection is List list) return list.Max(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Max(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + public static T Min(this IReadOnlyCollection collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null || collection.Count == 0) return double.MaxValue.ToUnit(); + if (collection is T?[] array) return array.Min(); + if (collection is List list) return list.Min(); + + T?[] sharedArray = ArrayPool.Shared.Rent(collection.Count); + int index = 0; + try + { + foreach (var item in collection) sharedArray[index++] = item; + return new ReadOnlySpan(sharedArray, 0, collection.Count).Min(); + } + finally { ArrayPool.Shared.Return(sharedArray); } + } + + // === IEnumerable === + public static T Sum(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return default; + + // Быстрый SIMD-путь для готовых коллекций за 0 аллокаций + if (collection is T?[] array) return array.Sum(); + if (collection is List list) return list.Sum(); + if (collection is ICollection col) return col.Sum(); + if (collection is IReadOnlyCollection 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() : default; + } + public static T Average(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.NaN.ToUnit(); + + if (collection is T?[] array) return array.Average(); + if (collection is List list) return list.Average(); + if (collection is ICollection col) return col.Average(); + if (collection is IReadOnlyCollection 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(); + + double avg = sum / count; + return avg.ToUnit(); + } + public static T Max(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.MinValue.ToUnit(); + + if (collection is T?[] array) return array.Max(); + if (collection is List list) return list.Max(); + if (collection is ICollection col) return col.Max(); + if (collection is IReadOnlyCollection 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() : double.MinValue.ToUnit(); + } + public static T Min(this IEnumerable collection) + where T : struct, IMensuraUnit, IEquatable + { + if (collection == null) return double.MaxValue.ToUnit(); + + if (collection is T?[] array) return array.Min(); + if (collection is List list) return list.Min(); + if (collection is ICollection col) return col.Min(); + if (collection is IReadOnlyCollection 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() : double.MaxValue.ToUnit(); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CastExtensions.cs b/QWERTYkez.Mensura/Extensions/CastExtensions.cs new file mode 100644 index 0000000..ccf88df --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CastExtensions.cs @@ -0,0 +1,65 @@ +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CastExtensions +{ + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static double ToDouble(this T unit) + where T : struct, IMensuraUnit, IEquatable + { + return Unsafe.As(ref unit); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static double ToDouble(this T? unit) + where T : struct, IMensuraUnit, IEquatable + { + T actual = unit.GetValueOrDefault(); + return Unsafe.As(ref actual); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static T ToUnit(this double val) + where T : struct, IMensuraUnit, IEquatable + { + return Unsafe.As(ref val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void SetCountUnsafe(this List list, int count) + { + // Берем адрес управляемого объекта List в памяти + // Объект передается по ref-ссылке, преобразуется в указатель + ref var mimic = ref Unsafe.As, ListLayoutMimic>(ref list); + + // Меняем приватный размер напрямую в памяти объекта! + mimic.Size = count; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static List WrapAsList(this T[] array) + { + // Создаём пустой список с нулевой ёмкостью + var list = new List(0); + + // Получаем внутреннюю структуру списка + ref var mimic = ref Unsafe.As, ListLayoutMimic>(ref list); + + // Подменяем массив и устанавливаем размер + mimic.Items = array; + mimic.Size = array.Length; + mimic.Version = 1; // любое ненулевое значение для консистенции + + return list; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static List WrapAsList(this T[] array) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + return Unsafe.As(ref array).WrapAsList(); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsDivideExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsDivideExtensions.cs new file mode 100644 index 0000000..0b3fd15 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsDivideExtensions.cs @@ -0,0 +1,588 @@ +using System.Buffers; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsDivideExtensions +{ + // === ReadOnlySpan === SIMD + public static void Divide(this ReadOnlySpan units, double divisor, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + // Вместо деления в цикле, умножаем на обратное число (invDivisor) + double invDivisor = 1.0 / divisor; + var vectorizedInvDivisor = new Vector(invDivisor); + + int vectorSize = Vector.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>(ref currentSrc); + var multiplied = vector * vectorizedInvDivisor; + + Unsafe.As>(ref currentDst) = multiplied; + } + + // Хвост + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * invDivisor; + } + } + public static void Divide(this ReadOnlySpan units, double divisor, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + + // Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов + ref var srcRef = ref MemoryMarshal.GetReference(units); + ref var dstRef = ref MemoryMarshal.GetReference(destination); + + int i = 0; + int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) + double invDivisor = 1.0 / divisor; + + // 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию + for (; i < unrollEnd; i += 4) + { + T? u0 = Unsafe.Add(ref srcRef, i); + T? u1 = Unsafe.Add(ref srcRef, i + 1); + T? u2 = Unsafe.Add(ref srcRef, i + 2); + T? u3 = Unsafe.Add(ref srcRef, i + 3); + + // Получаем ref-ссылки на ячейки назначения (zero-cost адресация) + ref var d0 = ref Unsafe.Add(ref dstRef, i); + ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); + ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); + ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); + + // Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага) + d0 = u0.HasValue ? (u0.Value.ToDouble() * invDivisor).ToUnit() : null; + d1 = u1.HasValue ? (u1.Value.ToDouble() * invDivisor).ToUnit() : null; + d2 = u2.HasValue ? (u2.Value.ToDouble() * invDivisor).ToUnit() : null; + d3 = u3.HasValue ? (u3.Value.ToDouble() * invDivisor).ToUnit() : 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() : null; + } + } + //SIMD + public static void Divide(this double dividend, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + var vectorizedDividend = new Vector(dividend); + int vectorSize = Vector.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>(ref currentSrc); + + // Векторное деление: константа делится на покомпонентные элементы массива + var divided = Vector.Divide(vectorizedDividend, vector); + + Unsafe.As>(ref currentDst) = divided; + } + + // Хвост + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = dividend / Unsafe.Add(ref srcRef, i); + } + } + public static void Divide(this double dividend, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + 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() : null; + d1 = u1.HasValue ? (dividend / u1.Value.ToDouble()).ToUnit() : null; + d2 = u2.HasValue ? (dividend / u2.Value.ToDouble()).ToUnit() : null; + d3 = u3.HasValue ? (dividend / u3.Value.ToDouble()).ToUnit() : 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() : null; + } + } + + // === Array === + public static T[] Divide(this T[] units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Divide(units, divisor, result); + return result; + } + public static T?[] Divide(this T?[] units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + public static T[] Divide(this double dividend, T[] units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Divide(dividend, units, result); + return result; + } + public static T?[] Divide(this double dividend, T?[] units) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + + // === List === + public static List Divide(this List units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Divide(CollectionsMarshal.AsSpan(units), divisor, resultArray); + return resultArray.WrapAsList(); + } + public static List Divide(this List units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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 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(); + } + } + return resultArray.WrapAsList(); + } + public static List Divide(this double dividend, List units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Divide(dividend, CollectionsMarshal.AsSpan(units), resultArray); + return resultArray.WrapAsList(); + } + public static List Divide(this double dividend, List units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new T?[count]; + ReadOnlySpan 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(); + } + } + return resultArray.WrapAsList(); + } + + // === ICollection === + public static ICollection Divide(this ICollection units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Divide(divisor); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Divide(new ReadOnlySpan(sharedArray, 0, count), divisor, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Divide(this ICollection units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Divide(divisor); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + srcSpan.Divide(divisor, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + public static ICollection Divide(this double dividend, ICollection units) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return dividend.Divide(list); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Divide(dividend, new ReadOnlySpan(sharedArray, 0, count), finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Divide(this double dividend, ICollection units) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return dividend.Divide(list); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + dividend.Divide(srcSpan, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IReadOnlyCollection === + public static IReadOnlyCollection Divide(this IReadOnlyCollection units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Divide(divisor); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Divide(sharedArray, divisor, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Divide(this IReadOnlyCollection units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Divide(divisor); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + double invDivisor = 1.0 / divisor; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (item.Value.ToDouble() * invDivisor).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + public static IReadOnlyCollection Divide(this double dividend, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T[] array) return array.Divide(dividend); + if (units is List list) return list.Divide(dividend); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Divide(dividend, sharedArray, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Divide(this double dividend, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T?[] array) return array.Divide(dividend); + if (units is List list) return list.Divide(dividend); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (dividend / item.Value.ToDouble()).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IEnumerable + yeild === + static IEnumerable DivideIterator(IEnumerable units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + double invDivisor = 1.0 / divisor; + foreach (var item in units) + yield return (item.ToDouble() * invDivisor).ToUnit(); + } + static IEnumerable DivideNullableIterator(IEnumerable units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + double invDivisor = 1.0 / divisor; + foreach (T? item in units) + yield return item.HasValue + ? (item.Value.ToDouble() * invDivisor).ToUnit() : null; + } + static IEnumerable DivideIterator(double dividend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (dividend / item.ToDouble()).ToUnit(); + } + static IEnumerable DivideNullableIterator(double dividend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + foreach (T? item in units) + yield return item.HasValue + ? (dividend / item.Value.ToDouble()).ToUnit() : null; + } + + // === IEnumerable === + public static IEnumerable Divide(this IEnumerable units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Divide(divisor); + if (units is List list) return list.Divide(divisor); + if (units is ICollection col) return col.Divide(divisor); + if (units is IReadOnlyCollection roc) return roc.Divide(divisor); + return DivideIterator(units, divisor); + } + public static IEnumerable Divide(this IEnumerable units, double divisor) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Divide(divisor); + if (units is List list) return list.Divide(divisor); + if (units is ICollection col) return col.Divide(divisor); + if (units is IReadOnlyCollection roc) return roc.Divide(divisor); + return DivideNullableIterator(units, divisor); + } + public static IEnumerable Divide(this double dividend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return dividend.Divide(array); + if (units is List list) return dividend.Divide(list); + if (units is ICollection col) return dividend.Divide(col); + if (units is IReadOnlyCollection roc) return dividend.Divide(roc); + return DivideIterator(dividend, units); + } + public static IEnumerable Divide(this double dividend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return dividend.Divide(array); + if (units is List list) return dividend.Divide(list); + if (units is ICollection col) return dividend.Divide(col); + if (units is IReadOnlyCollection roc) return dividend.Divide(roc); + return DivideNullableIterator(dividend, units); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsMinusExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsMinusExtensions.cs new file mode 100644 index 0000000..fca263e --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsMinusExtensions.cs @@ -0,0 +1,575 @@ +using System.Buffers; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsMinusExtensions +{ + // === ReadOnlySpan === SIMD + public static void Minus(this ReadOnlySpan units, double subtrahend, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + var vectorizedSubtrahend = new Vector(subtrahend); + int vectorSize = Vector.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>(ref currentSrc); + var subtracted = vector - vectorizedSubtrahend; + + Unsafe.As>(ref currentDst) = subtracted; + } + + // Хвост + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) - subtrahend; + } + } + public static void Minus(this ReadOnlySpan units, double subtrahend, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + 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() : null; + d1 = u1.HasValue ? (u1.Value.ToDouble() - subtrahend).ToUnit() : null; + d2 = u2.HasValue ? (u2.Value.ToDouble() - subtrahend).ToUnit() : null; + d3 = u3.HasValue ? (u3.Value.ToDouble() - subtrahend).ToUnit() : 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() : null; + } + } + //SIMD + public static void Minus(this double minuend, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + var vectorizedMinuend = new Vector(minuend); + int vectorSize = Vector.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>(ref currentSrc); + + // Векторное вычитание: константа уменьшается на покомпонентные элементы массива + var subtracted = vectorizedMinuend - vector; + + Unsafe.As>(ref currentDst) = subtracted; + } + + // Хвост + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = minuend - Unsafe.Add(ref srcRef, i); + } + } + public static void Minus(this double minuend, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + 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() : null; + d1 = u1.HasValue ? (minuend - u1.Value.ToDouble()).ToUnit() : null; + d2 = u2.HasValue ? (minuend - u2.Value.ToDouble()).ToUnit() : null; + d3 = u3.HasValue ? (minuend - u3.Value.ToDouble()).ToUnit() : 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() : null; + } + } + + // === Array === + public static T[] Minus(this T[] units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Minus(units, subtrahend, result); + return result; + } + public static T?[] Minus(this T?[] units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + public static T[] Minus(this double minuend, T[] units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Minus(minuend, units, result); + return result; + } + public static T?[] Minus(this double minuend, T?[] units) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + + // === List === + public static List Minus(this List units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Minus(CollectionsMarshal.AsSpan(units), subtrahend, resultArray); + return resultArray.WrapAsList(); + } + public static List Minus(this List units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new T?[count]; + ReadOnlySpan 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(); + } + } + return resultArray.WrapAsList(); + } + public static List Minus(this double minuend, List units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Minus(minuend, CollectionsMarshal.AsSpan(units), resultArray); + return resultArray.WrapAsList(); + } + public static List Minus(this double minuend, List units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new T?[count]; + ReadOnlySpan 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(); + } + } + return resultArray.WrapAsList(); + } + + // === ICollection === + public static ICollection Minus(this ICollection units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Minus(subtrahend); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Minus(new ReadOnlySpan(sharedArray, 0, count), subtrahend, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Minus(this ICollection units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Minus(subtrahend); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + srcSpan.Minus(subtrahend, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + public static ICollection Minus(this double minuend, ICollection units) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return minuend.Minus(list); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Minus(minuend, new ReadOnlySpan(sharedArray, 0, count), finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Minus(this double minuend, ICollection units) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return minuend.Minus(list); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + minuend.Minus(srcSpan, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IReadOnlyCollection === + public static IReadOnlyCollection Minus(this IReadOnlyCollection units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T[] array) return array.Multiply(subtrahend); + if (units is List list) return list.Multiply(subtrahend); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Minus(sharedArray, subtrahend, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Minus(this IReadOnlyCollection units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T?[] array) return array.Multiply(subtrahend); + if (units is List list) return list.Multiply(subtrahend); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (item.Value.ToDouble() - subtrahend).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + public static IReadOnlyCollection Minus(this double minuend, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T[] array) return array.Multiply(minuend); + if (units is List list) return list.Multiply(minuend); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Minus(minuend, sharedArray, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Minus(this double minuend, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T?[] array) return array.Multiply(minuend); + if (units is List list) return list.Multiply(minuend); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (minuend - item.Value.ToDouble()).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IEnumerable + yeild === + static IEnumerable MinusIterator(IEnumerable units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (item.ToDouble() - subtrahend).ToUnit(); + } + static IEnumerable MinusNullableIterator(IEnumerable units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + foreach (T? item in units) + yield return item.HasValue + ? (item.Value.ToDouble() - subtrahend).ToUnit() : null; + } + static IEnumerable MinusIterator(double minuend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (minuend - item.ToDouble()).ToUnit(); + } + static IEnumerable MinusNullableIterator(double minuend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + foreach (T? item in units) + yield return item.HasValue + ? (minuend - item.Value.ToDouble()).ToUnit() : null; + } + + // === IEnumerable === + public static IEnumerable Minus(this IEnumerable units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Minus(subtrahend); + if (units is List list) return list.Minus(subtrahend); + if (units is ICollection col) return col.Minus(subtrahend); + if (units is IReadOnlyCollection roc) return roc.Minus(subtrahend); + return MinusIterator(units, subtrahend); + } + public static IEnumerable Minus(this IEnumerable units, double subtrahend) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Minus(subtrahend); + if (units is List list) return list.Minus(subtrahend); + if (units is ICollection col) return col.Minus(subtrahend); + if (units is IReadOnlyCollection roc) return roc.Minus(subtrahend); + return MinusNullableIterator(units, subtrahend); + } + public static IEnumerable Minus(this double minuend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return minuend.Minus(array); + if (units is List list) return minuend.Minus(list); + if (units is ICollection col) return minuend.Minus(col); + if (units is IReadOnlyCollection roc) return minuend.Minus(roc); + return MinusIterator(minuend, units); + } + public static IEnumerable Minus(this double minuend, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return minuend.Minus(array); + if (units is List list) return minuend.Minus(list); + if (units is ICollection col) return minuend.Minus(col); + if (units is IReadOnlyCollection roc) return minuend.Minus(roc); + return MinusNullableIterator(minuend, units); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsMultiplyExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsMultiplyExtensions.cs new file mode 100644 index 0000000..7850e6f --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsMultiplyExtensions.cs @@ -0,0 +1,346 @@ +using System.Buffers; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsMultiplyExtensions +{ + // === ReadOnlySpan === SIMD + public static void Multiply(this ReadOnlySpan units, double multiplicator, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + var vectorizedMultiplicator = new Vector(multiplicator); + int vectorSize = Vector.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>(ref currentSrc); + var multiplied = vector * vectorizedMultiplicator; + + Unsafe.As>(ref currentDst) = multiplied; + } + + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) * multiplicator; + } + } + public static void Multiply(this ReadOnlySpan units, double multiplicator, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + // Получаем прямые неуправляемые ref-ссылки на начало буферов за 0 тактов + ref var srcRef = ref MemoryMarshal.GetReference(units); + ref var dstRef = ref MemoryMarshal.GetReference(destination); + + int i = 0; + int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) + + // 1. ОСНОВНОЙ ЦИКЛ: Обрабатываем конвейером по 4 элемента за итерацию + for (; i < unrollEnd; i += 4) + { + T? u0 = Unsafe.Add(ref srcRef, i); + T? u1 = Unsafe.Add(ref srcRef, i + 1); + T? u2 = Unsafe.Add(ref srcRef, i + 2); + T? u3 = Unsafe.Add(ref srcRef, i + 3); + + // Получаем ref-ссылки на ячейки назначения (zero-cost адресация) + ref var d0 = ref Unsafe.Add(ref dstRef, i); + ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); + ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); + ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); + + // Пишем строго по месту. Если HasValue == false, в ячейку dX запишется null (сбросятся байты флага) + d0 = u0.HasValue ? (u0.Value.ToDouble() * multiplicator).ToUnit() : null; + d1 = u1.HasValue ? (u1.Value.ToDouble() * multiplicator).ToUnit() : null; + d2 = u2.HasValue ? (u2.Value.ToDouble() * multiplicator).ToUnit() : null; + d3 = u3.HasValue ? (u3.Value.ToDouble() * multiplicator).ToUnit() : 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() : null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Multiply(this double multiplicator, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator, destination); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Multiply(this double multiplicator, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator, destination); + + // === Array === + public static T[] Multiply(this T[] units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Multiply(units, multiplicator, result); + return result; + } + public static T?[] Multiply(this T?[] units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Multiply(this double multiplicator, T[] units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T?[] Multiply(this double multiplicator, T?[] units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + // === List === + public static List Multiply(this List units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Multiply(CollectionsMarshal.AsSpan(units), multiplicator, resultArray); + return resultArray.WrapAsList(); + } + public static List Multiply(this List units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new T?[count]; + ReadOnlySpan 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(); + } + } + return resultArray.WrapAsList(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List Multiply(this double multiplicator, List units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List Multiply(this double multiplicator, List units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + // === ICollection === + public static ICollection Multiply(this ICollection units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Multiply(multiplicator); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Multiply(new ReadOnlySpan(sharedArray, 0, count), multiplicator, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Multiply(this ICollection units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Multiply(multiplicator); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + srcSpan.Multiply(multiplicator, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ICollection Multiply(this double multiplicator, ICollection units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ICollection Multiply(this double multiplicator, ICollection units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + // === IReadOnlyCollection === + public static IReadOnlyCollection Multiply(this IReadOnlyCollection units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Multiply(multiplicator); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Multiply(sharedArray, multiplicator, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Multiply(this IReadOnlyCollection units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Multiply(multiplicator); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (item.Value.ToDouble() * multiplicator).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyCollection Multiply(this double multiplicator, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyCollection Multiply(this double multiplicator, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable => units.Multiply(multiplicator); + + // === IEnumerable + yeild === + static IEnumerable MultiplyIterator(IEnumerable units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (item.ToDouble() * multiplicator).ToUnit(); + } + static IEnumerable MultiplyNullableIterator(IEnumerable units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + foreach (T? item in units) + yield return item.HasValue + ? (item.Value.ToDouble() * multiplicator).ToUnit() : null; + } + + // === IEnumerable === + public static IEnumerable Multiply(this IEnumerable units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Multiply(multiplicator); + if (units is List list) return list.Multiply(multiplicator); + if (units is ICollection col) return col.Multiply(multiplicator); + if (units is IReadOnlyCollection roc) return roc.Multiply(multiplicator); + return MultiplyIterator(units, multiplicator); + } + public static IEnumerable Multiply(this IEnumerable units, double multiplicator) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Multiply(multiplicator); + if (units is List list) return list.Multiply(multiplicator); + if (units is ICollection col) return col.Multiply(multiplicator); + if (units is IReadOnlyCollection roc) return roc.Multiply(multiplicator); + return MultiplyNullableIterator(units, multiplicator); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Multiply(this double multiplicator, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable => Multiply(units, multiplicator); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Multiply(this double multiplicator, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable => Multiply(units, multiplicator); +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsPlusExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsPlusExtensions.cs new file mode 100644 index 0000000..62ac481 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsPlusExtensions.cs @@ -0,0 +1,346 @@ +using System.Buffers; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsPlusExtensions +{ + // === ReadOnlySpan === SIMD + public static void Plus(this ReadOnlySpan units, double summand, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + var vectorizedSummand = new Vector(summand); + int vectorSize = Vector.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>(ref currentSrc); + var added = vector + vectorizedSummand; + + Unsafe.As>(ref currentDst) = added; + } + + // Хвост + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Unsafe.Add(ref srcRef, i) + summand; + } + } + public static void Plus(this ReadOnlySpan units, double summand, Span destination) + where T : struct, IMensuraUnit, IEquatable + { + 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() : null; + d1 = u1.HasValue ? (u1.Value.ToDouble() + summand).ToUnit() : null; + d2 = u2.HasValue ? (u2.Value.ToDouble() + summand).ToUnit() : null; + d3 = u3.HasValue ? (u3.Value.ToDouble() + summand).ToUnit() : 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() : null; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Plus(this double summand, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand, destination); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Plus(this double summand, ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand, destination); + + // === Array === + public static T[] Plus(this T[] units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new T[len]; + Plus(units, summand, result); + return result; + } + public static T?[] Plus(this T?[] units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + 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(); + } + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T[] Plus(this double summand, T[] units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T?[] Plus(this double summand, T?[] units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + // === List === + public static List Plus(this List units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new T[len]; + Plus(CollectionsMarshal.AsSpan(units), summand, resultArray); + return resultArray.WrapAsList(); + } + public static List Plus(this List units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new T?[count]; + ReadOnlySpan 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(); + } + } + return resultArray.WrapAsList(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List Plus(this double summand, List units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static List Plus(this double summand, List units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + // === ICollection === + public static ICollection Plus(this ICollection units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Plus(summand); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + units.CopyTo(sharedArray, 0); + Plus(new ReadOnlySpan(sharedArray, 0, count), summand, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Plus(this ICollection units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Plus(summand); + + var resultArray = new T?[count]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + try + { + units.CopyTo(sharedNullableArray, 0); + var srcSpan = new ReadOnlySpan(sharedNullableArray, 0, count); + srcSpan.Plus(summand, resultArray); + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ICollection Plus(this double summand, ICollection units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ICollection Plus(this double summand, ICollection units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + // === IReadOnlyCollection === + public static IReadOnlyCollection Plus(this IReadOnlyCollection units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Plus(summand); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new T[count]; + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Plus(sharedArray, summand, finalResult.AsSpan()); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static IReadOnlyCollection Plus(this IReadOnlyCollection units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + 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 list) return list.Plus(summand); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(count); + var resultArray = new T?[count]; + try + { + int index = 0; + foreach (var item in units) sharedNullableArray[index++] = item; + for (int i = 0; i < count; i++) + { + T? item = sharedNullableArray[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = (item.Value.ToDouble() + summand).ToUnit(); + } + } + return resultArray; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyCollection Plus(this double summand, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IReadOnlyCollection Plus(this double summand, IReadOnlyCollection units) + where T : struct, IMensuraUnit, IEquatable => units.Plus(summand); + + // === IEnumerable + yeild === + static IEnumerable PlusIterator(IEnumerable units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (item.ToDouble() + summand).ToUnit(); + } + static IEnumerable PlusNullableIterator(IEnumerable units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + foreach (T? item in units) + yield return item.HasValue + ? (item.Value.ToDouble() + summand).ToUnit() : null; + } + + // === IEnumerable === + public static IEnumerable Plus(this IEnumerable units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Plus(summand); + if (units is List list) return list.Plus(summand); + if (units is ICollection col) return col.Plus(summand); + if (units is IReadOnlyCollection roc) return roc.Plus(summand); + return PlusIterator(units, summand); + } + public static IEnumerable Plus(this IEnumerable units, double summand) + where T : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Plus(summand); + if (units is List list) return list.Plus(summand); + if (units is ICollection col) return col.Plus(summand); + if (units is IReadOnlyCollection roc) return roc.Plus(summand); + return PlusNullableIterator(units, summand); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Plus(this double summand, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable => Plus(units, summand); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Plus(this double summand, IEnumerable units) + where T : struct, IMensuraUnit, IEquatable => Plus(units, summand); +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsPowExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsPowExtensions.cs new file mode 100644 index 0000000..5e48e66 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsPowExtensions.cs @@ -0,0 +1,710 @@ +using System.Buffers; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsPowExtensions +{ + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static double QuickPow(this double base_Value, int exp) + { + switch (exp) + { + case 0: return 1.0; + case 1: return base_Value; + case 2: return base_Value * base_Value; + case 3: return base_Value * base_Value * base_Value; + case 4: { double x2 = base_Value * base_Value; return x2 * x2; } + case -1: return 1.0 / base_Value; + case -2: return 1.0 / (base_Value * base_Value); + default: return Math.Pow(base_Value, exp); + } + } + + + // === ЦЕЛАЯ СТЕПЕНЬ ========================================== + + // === ReadOnlySpan === SIMD + internal static void Pow(this ReadOnlySpan units, int power, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + int len = units.Length; + if (len == 0) return; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(destination); + + int vectorSize = Vector.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>(ref currentSrc); + + // Векторный аналог быстрого (бинарного) возведения в степень + var result = Vector.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.One, result); + } + + // Сохраняем итоговый вектор + Unsafe.As>(ref currentDst) = result; + } + + // Хвост массива (или обычный расчет, если SIMD не поддерживается) + var absPower = Math.Abs(power); + for (; i < len; i++) + { + double @base = Unsafe.Add(ref srcRef, i); + double result = 1.0; + int currentPower = absPower; + + while (currentPower > 0) + { + if ((currentPower & 1) == 1) result *= @base; + @base *= @base; + currentPower >>= 1; + } + + Unsafe.Add(ref dstRef, i) = power < 0 ? 1.0 / result : result; + } + } + internal static void Pow(this ReadOnlySpan units, int power, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + int len = units.Length; + if (len == 0) return; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + // Получаем прямые ref-ссылки на начало буферов за 0 тактов процессора + ref var srcRef = ref MemoryMarshal.GetReference(units); + ref var dstRef = ref MemoryMarshal.GetReference(destination); + + int i = 0; + int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) + + // 1. ОСНОВНОЙ ЦИКЛ: Параллельный конвейер по 4 элемента за итерацию + for (; i < unrollEnd; i += 4) + { + T? u0 = Unsafe.Add(ref srcRef, i); + T? u1 = Unsafe.Add(ref srcRef, i + 1); + T? u2 = Unsafe.Add(ref srcRef, i + 2); + T? u3 = Unsafe.Add(ref srcRef, i + 3); + + // Получаем ref-ссылки на целевые ячейки типа R? (zero-cost адресация) + ref var d0 = ref Unsafe.Add(ref dstRef, i); + ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); + ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); + ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); + + // Выполняем быструю бинарную математику и трансформируем тип из T в R по месту + d0 = u0.HasValue ? u0.Value.ToDouble().QuickPow(power).ToUnit() : null; + d1 = u1.HasValue ? u1.Value.ToDouble().QuickPow(power).ToUnit() : null; + d2 = u2.HasValue ? u2.Value.ToDouble().QuickPow(power).ToUnit() : null; + d3 = u3.HasValue ? u3.Value.ToDouble().QuickPow(power).ToUnit() : null; + } + + // 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 шчку) + for (; i < len; i++) + { + T? unit = Unsafe.Add(ref srcRef, i); + ref var dst = ref Unsafe.Add(ref dstRef, i); + + dst = unit.HasValue ? unit.Value.ToDouble().QuickPow(power).ToUnit() : null; + } + } + + // === Array === + internal static R[] Pow(this T[] units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new R[len]; + Pow(units, power, result); + return result; + } + internal static R?[] Pow(this T?[] units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var result = new R?[len]; + Pow(units, power, result); + return result; + } + + // === List === + internal static List Pow(this List units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new R[count]; + Pow(CollectionsMarshal.AsSpan(units), power, resultArray); + return resultArray.WrapAsList(); + } + internal static List Pow(this List units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + + var resultArray = new R?[count]; + Pow(CollectionsMarshal.AsSpan(units), power, resultArray); + return resultArray.WrapAsList(); + } + + // === ICollection === + internal static ICollection Pow(this ICollection units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + ICollection result = []; + foreach (var item in units) + result.Add(item.ToDouble().QuickPow(power).ToUnit()); + return result; + } + internal static ICollection Pow(this ICollection units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + ICollection result = []; + foreach (var item in units) + result.Add((item.HasValue ? item.Value.ToDouble().QuickPow(power) : 0d).ToUnit()); + return result; + } + + // === IReadOnlyCollection === + internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + T[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new R[count]; + + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Pow(sharedArray, power, finalResult); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int count = units.Count; + if (count == 0) return []; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + T?[] sharedArray = ArrayPool.Shared.Rent(count); + var finalResult = new R?[count]; + + try + { + int index = 0; + foreach (var item in units) sharedArray[index++] = item; + Pow(sharedArray, power, finalResult); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + + // === IEnumerable + yeild === + static IEnumerable PowIterator(IEnumerable units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return (item.ToDouble().QuickPow(power)).ToUnit(); + } + static IEnumerable PowNullableIterator(IEnumerable units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return item.HasValue + ? item.Value.ToDouble().QuickPow(power).ToUnit() : null; + } + + // === IEnumerable === + internal static IEnumerable Pow(this IEnumerable units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + if (units is ICollection col) return col.Pow(power); + if (units is IReadOnlyCollection roc) return roc.Pow(power); + else return PowIterator(units, power); + } + internal static IEnumerable Pow(this IEnumerable units, int power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + if (units is ICollection col) return col.Pow(power); + if (units is IReadOnlyCollection roc) return roc.Pow(power); + else return PowNullableIterator(units, power); + } + + + // === ДРОБНАЯ СТЕПЕНЬ ========================================== + + // === ReadOnlySpan === + internal static void Pow(this ReadOnlySpan units, double power, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + int len = units.Length; + if (len == 0) return; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(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>(ref currentSrc); + + // Аппаратный корень через инструкцию vsqrtpd + var sqrtV = Avx.Sqrt(v); + + // Совместимый с .NET 6 способ записи вектора в ref double + ref double currentDst = ref Unsafe.Add(ref dstRef, i); + Unsafe.As>(ref currentDst) = sqrtV; + } + } + + // Хвост для корня + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i)); + } + return; + } + + // 3. ОБЩИЙ СЛУЧАЙ ДЛЯ СЛОЖНЫХ СТЕПЕНЕЙ + // Из-за ограничений .NET 6 здесь мы разворачиваем цикл вручную на 4 элемента, + // но делаем это через ref-ссылки без использования fixed-указателей + if (len >= 4) + { + int simdEnd = len & ~3; + for (; i < simdEnd; i += 4) + { + ref double sRef = ref Unsafe.Add(ref srcRef, i); + ref double dRef = ref Unsafe.Add(ref dstRef, i); + + // JIT попытается это автовекторизовать, но даже без нее + // этот код работает быстрее fixed за счет отсутствия пиннинга памяти GC + Unsafe.Add(ref dRef, 0) = Math.Pow(Unsafe.Add(ref sRef, 0), power); + Unsafe.Add(ref dRef, 1) = Math.Pow(Unsafe.Add(ref sRef, 1), power); + Unsafe.Add(ref dRef, 2) = Math.Pow(Unsafe.Add(ref sRef, 2), power); + Unsafe.Add(ref dRef, 3) = Math.Pow(Unsafe.Add(ref sRef, 3), power); + } + } + + // Хвост массива + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Math.Pow(Unsafe.Add(ref srcRef, i), power); + } + } + internal static void Pow(this ReadOnlySpan units, double power, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + int len = units.Length; + if (len == 0) return; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + for (int i = 0; i < len; i++) + { + T? item = units[i]; + if (item.HasValue) + { + // Прямая ref-запись в переданный снаружи destination + ref var dst = ref destination[i]; + dst = Math.Pow(item.Value.ToDouble(), power).ToUnit(); + } + } + } + + // === Array === + internal static R[] Pow(this T[] units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Length; + if (len == 0) return []; + + var finalResult = new R[len]; + if (power != 0.5) + { + for (int i = 0; i < len; i++) + { + ref var dst = ref finalResult[i]; + dst = Math.Pow(units[i].ToDouble(), power).ToUnit(); + } + } + else MemoryMarshal.Cast(units).Sqrt(finalResult); + return finalResult; + } + internal static R?[] Pow(this T?[] units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units.Length == 0) return []; + + var result = (R?[])units.Clone(); + int len = result.Length; + for (int i = 0; i < len; i++) + { + ref var item = ref result[i]; + if (item.HasValue) + { + item = Math.Pow(item.Value.ToDouble(), power).ToUnit(); + } + } + return result; + } + + // === List === + internal static List Pow(this List units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new R[len]; + var srcSpan = CollectionsMarshal.AsSpan(units); + if (power == 0.5) + { + ReadOnlySpan srcAsR = MemoryMarshal.Cast(srcSpan); + srcAsR.Sqrt(resultArray); + } + else + { + Span dstSpan = resultArray.AsSpan(); + for (int i = 0; i < len; i++) + { + ref var dst = ref dstSpan[i]; + dst = Math.Pow(srcSpan[i].ToDouble(), power).ToUnit(); + } + } + return resultArray.WrapAsList(); + } + internal static List Pow(this List units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new R?[len]; + var srcSpan = CollectionsMarshal.AsSpan(units); + for (int i = 0; i < len; i++) + { + T? item = srcSpan[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = Math.Pow(item.Value.ToDouble(), power).ToUnit(); + } + } + return resultArray.WrapAsList(); + } + + // === ICollection === + internal static ICollection Pow(this ICollection units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + T[] sharedArray = ArrayPool.Shared.Rent(len); + var finalResult = new R[len]; + try + { + units.CopyTo(sharedArray, 0); + if (power == 0.5) + { + MemoryMarshal.Cast(sharedArray).Sqrt(finalResult); + } + else + { + for (int i = 0; i < len; i++) + finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit(); + } + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + internal static ICollection Pow(this ICollection units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(len); + var finalResult = new R?[len]; + try + { + units.CopyTo(sharedNullableArray, 0); + for (int i = 0; i < len; i++) + { + T? item = sharedNullableArray[i]; + finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : default; + } + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IReadOnlyCollection === + internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + var finalResult = new R[len]; + var sharedArray = ArrayPool.Shared.Rent(len); + try + { + if (units is ICollection collection) + { + collection.CopyTo(sharedArray, 0); + } + else + { + int idx = 0; + foreach (var item in units) sharedArray[idx++] = item; + } + + if (power != 0.5) + { + for (int i = 0; i < len; i++) + finalResult[i] = Math.Pow(sharedArray[i].ToDouble(), power).ToUnit(); + } + else MemoryMarshal.Cast(sharedArray).Sqrt(finalResult); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + internal static IReadOnlyCollection Pow(this IReadOnlyCollection units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + + var finalResult = new R?[len]; + T?[] sharedNullableArray = ArrayPool.Shared.Rent(len); + + try + { + if (units is ICollection collection) + { + collection.CopyTo(sharedNullableArray, 0); + } + else + { + int idx = 0; + foreach (T? item in units) sharedNullableArray[idx++] = item; + } + + for (int i = 0; i < len; i++) + { + T? item = sharedNullableArray[i]; + finalResult[i] = item.HasValue ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : default; + } + + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IEnumerable + yield === + internal static IEnumerable PowIterator(this IEnumerable units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return Math.Pow(item.ToDouble(), power).ToUnit(); + } + internal static IEnumerable PowNullableIterator(this IEnumerable units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return item.HasValue + ? Math.Pow(item.Value.ToDouble(), power).ToUnit() : null; + } + + // === IEnumerable === + internal static IEnumerable Pow(this IEnumerable units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + if (units is ICollection col) return col.Pow(power); + if (units is IReadOnlyCollection roc) return roc.Pow(power); + else return PowIterator(units, power); + } + internal static IEnumerable Pow(this IEnumerable units, double power) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Pow(power); + if (units is List list) return list.Pow(power); + if (units is ICollection col) return col.Pow(power); + if (units is IReadOnlyCollection roc) return roc.Pow(power); + else return PowNullableIterator(units, power); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/CollectionsSqrtExtensions.cs b/QWERTYkez.Mensura/Extensions/CollectionsSqrtExtensions.cs new file mode 100644 index 0000000..8911616 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/CollectionsSqrtExtensions.cs @@ -0,0 +1,269 @@ +using System.Buffers; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace QWERTYkez.Mensura.Extensions; + +public static partial class CollectionsSqrtExtensions +{ + // === ReadOnlySpan === SIMD + internal static void Sqrt(this ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + ReadOnlySpan srcDouble = MemoryMarshal.Cast(units); + Span dstDouble = MemoryMarshal.Cast(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>(ref currentSrc); + + // Аппаратный корень на уровне CPU + var sqrtV = Avx.Sqrt(v); + + // Выгружаем результат обратно в память назначения за 1 такт + Unsafe.As>(ref currentDst) = sqrtV; + } + } + // 2. ПУТЬ VECTOR (ARM64 / Apple Silicon / Старые CPU без AVX) + else if (Vector.IsHardwareAccelerated && len >= Vector.Count) + { + int vCount = Vector.Count; + int simdEnd = len & ~(vCount - 1); + + for (; i < simdEnd; i += vCount) + { + // Используем Span для кроссплатформенного создания вектора + var v = new Vector(srcDouble.Slice(i, vCount)); + + // Кроссплатформенный аппаратный корень (на ARM превратится в NEON инструкцию) + var sqrtV = Vector.SquareRoot(v); + + // Копируем напрямую в целевой Span + sqrtV.CopyTo(dstDouble.Slice(i, vCount)); + } + } + + // 3. Хвост массива (или обычный расчет, если SIMD на процессоре недоступен) + for (; i < len; i++) + { + Unsafe.Add(ref dstRef, i) = Math.Sqrt(Unsafe.Add(ref srcRef, i)); + } + } + public static void Sqrt(this ReadOnlySpan units, Span destination) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units.IsEmpty) return; + int len = units.Length; + if (len > destination.Length) + throw new ArgumentException("Целевой буфер destination меньше исходного source."); + + // Получаем прямые ref-ссылки на начало буферов за 0 тактов процессора + ref var srcRef = ref MemoryMarshal.GetReference(units); + ref var dstRef = ref MemoryMarshal.GetReference(destination); + + int i = 0; + int unrollEnd = len & ~3; // Граница развернутого цикла (кратная 4) + + // 1. ОСНОВНОЙ ЦИКЛ: Конвейерная обработка по 4 элемента за итерацию + for (; i < unrollEnd; i += 4) + { + T? u0 = Unsafe.Add(ref srcRef, i); + T? u1 = Unsafe.Add(ref srcRef, i + 1); + T? u2 = Unsafe.Add(ref srcRef, i + 2); + T? u3 = Unsafe.Add(ref srcRef, i + 3); + + // Получаем ref-ссылки на целевые ячейки типа R? (zero-cost адресация) + ref var d0 = ref Unsafe.Add(ref dstRef, i); + ref var d1 = ref Unsafe.Add(ref dstRef, i + 1); + ref var d2 = ref Unsafe.Add(ref dstRef, i + 2); + ref var d3 = ref Unsafe.Add(ref dstRef, i + 3); + + // Считаем нативный корень прямо в регистрах и трансформируем тип из T в R по месту + d0 = u0.HasValue ? Math.Sqrt(u0.Value.ToDouble()).ToUnit() : null; + d1 = u1.HasValue ? Math.Sqrt(u1.Value.ToDouble()).ToUnit() : null; + d2 = u2.HasValue ? Math.Sqrt(u2.Value.ToDouble()).ToUnit() : null; + d3 = u3.HasValue ? Math.Sqrt(u3.Value.ToDouble()).ToUnit() : null; + } + + // 2. ХВОСТ ЦИКЛА: Довычисляем остаток элементов (от 1 до 3 штук) + for (; i < len; i++) + { + T? unit = Unsafe.Add(ref srcRef, i); + ref var dst = ref Unsafe.Add(ref dstRef, i); + dst = unit.HasValue ? Math.Sqrt(unit.Value.ToDouble()).ToUnit() : null; + } + } + + // === Array === + public static R[] Sqrt(this T[] units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units.Length == 0) return []; + + var result = new R[units.Length]; + Sqrt(units, result); + return result; + } + public static R?[] Sqrt(this T?[] units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units.Length == 0) return []; + + var result = new R?[units.Length]; + Sqrt(units, result); + return result; + } + + // === List === + public static List Sqrt(this List units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new R[len]; + Sqrt(CollectionsMarshal.AsSpan(units), resultArray); + return resultArray.WrapAsList(); + } + internal static List Sqrt(this List units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + + var resultArray = new R?[len]; + var srcSpan = CollectionsMarshal.AsSpan(units); + for (int i = 0; i < len; i++) + { + T? item = srcSpan[i]; + if (item.HasValue) + { + ref var dst = ref resultArray[i]; + dst = Math.Sqrt(item.Value.ToDouble()).ToUnit(); + } + } + return resultArray.WrapAsList(); + } + + // === ICollection === + public static ICollection Sqrt(this ICollection units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T[] array) return array.Sqrt(); + if (units is List list) return list.Sqrt(); + + T[] sharedArray = ArrayPool.Shared.Rent(len); + var finalResult = new R[len]; + try + { + units.CopyTo(sharedArray, 0); + MemoryMarshal.Cast(sharedArray).Sqrt(finalResult); + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedArray); + } + } + public static ICollection Sqrt(this ICollection units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + int len = units.Count; + if (len == 0) return []; + if (units is T?[] array) return array.Sqrt(); + if (units is List list) return list.Sqrt(); + + T?[] sharedNullableArray = ArrayPool.Shared.Rent(len); + var finalResult = new R?[len]; + try + { + units.CopyTo(sharedNullableArray, 0); + for (int i = 0; i < len; i++) + { + T? item = sharedNullableArray[i]; + finalResult[i] = item.HasValue ? Math.Sqrt(item.Value.ToDouble()).ToUnit() : default; + } + return finalResult; + } + finally + { + ArrayPool.Shared.Return(sharedNullableArray); + } + } + + // === IEnumerable + yield === + public static IEnumerable SqrtIterator(this IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return Math.Sqrt(item.ToDouble()).ToUnit(); + } + public static IEnumerable SqrtNullableIterator(this IEnumerable units) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + foreach (var item in units) + yield return item.HasValue + ? Math.Sqrt(item.Value.ToDouble()).ToUnit() : null; + } + + // === IEnumerable === + internal static IEnumerable Sqrt(this IEnumerable units, double Sqrter) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T[] array) return array.Sqrt(Sqrter); + if (units is List list) return list.Sqrt(Sqrter); + if (units is ICollection col) return col.Sqrt(Sqrter); + if (units is IReadOnlyCollection roc) return roc.Sqrt(Sqrter); + else return SqrtIterator(units); + } + internal static IEnumerable Sqrt(this IEnumerable units, double Sqrter) + where T : struct, IMensuraUnit, IEquatable + where R : struct, IMensuraUnit, IEquatable + { + if (units is null) return null!; + if (units is T?[] array) return array.Sqrt(Sqrter); + if (units is List list) return list.Sqrt(Sqrter); + if (units is ICollection col) return col.Sqrt(Sqrter); + if (units is IReadOnlyCollection roc) return roc.Sqrt(Sqrter); + else return SqrtNullableIterator(units); + } +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions/ToDouble.cs b/QWERTYkez.Mensura/Extensions/ToDouble.cs new file mode 100644 index 0000000..eeb4007 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions/ToDouble.cs @@ -0,0 +1,40 @@ +using System.Globalization; + +namespace QWERTYkez.Mensura.Extensions; + +internal static partial class ToDoubleExtensions +{ + internal static double ToDouble(this sbyte number) => Convert.ToDouble(number); + internal static double ToDouble(this short number) => Convert.ToDouble(number); + internal static double ToDouble(this int number) => Convert.ToDouble(number); + internal static double ToDouble(this long number) => Convert.ToDouble(number); + internal static double ToDouble(this byte number) => Convert.ToDouble(number); + internal static double ToDouble(this ushort number) => Convert.ToDouble(number); + internal static double ToDouble(this uint number) => Convert.ToDouble(number); + internal static double ToDouble(this ulong number) => Convert.ToDouble(number); + internal static double ToDouble(this nint number) => Convert.ToDouble(number); + internal static double ToDouble(this nuint number) => Convert.ToDouble(number); + internal static double ToDouble(this float number) => Convert.ToDouble(number); + internal static double ToDouble(this decimal number) => Convert.ToDouble(number); + + internal static double ToDouble(this sbyte? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this short? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this int? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this long? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this byte? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this ushort? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this uint? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this ulong? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this nint? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this nuint? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this float? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this decimal? number) => number is null ? 0d : Convert.ToDouble(number); + +#if NET7_0_OR_GREATER + internal static double ToDouble(this Int128 number) => Convert.ToDouble(number); + internal static double ToDouble(this UInt128 number) => Convert.ToDouble(number); + + internal static double ToDouble(this Int128? number) => number is null ? 0d : Convert.ToDouble(number); + internal static double ToDouble(this UInt128? number) => number is null ? 0d : Convert.ToDouble(number); +#endif +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/Extensions2.cs b/QWERTYkez.Mensura/Extensions2.cs new file mode 100644 index 0000000..1746ff0 --- /dev/null +++ b/QWERTYkez.Mensura/Extensions2.cs @@ -0,0 +1,338 @@ +using System.Buffers; +using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; + +namespace QWERTYkez.Mensura; + +public static partial class Extensions2 +{ + + + + + + + + // ========================================== + // CORE + // ========================================== + + + + + + + // ========================================== + // === MULTIPLY === + // ========================================== + + + + + + + // ========================================== + // === DIVIDE === + // ========================================== + + + + + + + // ========================================== + // === PLUS === + // ========================================== + + + + + + + // ========================================== + // === MINUS === + // ========================================== + + + + + + + + + + + + + + + + + + + + + + + + + + + //internal static U Protect(this U? metric) where U : class, IMetric, new() => metric ?? new(); + //internal static C Protect(this C? collection) + // where C : IMetricCollection, new() where U : class, IMetric, new() => collection ?? new(); + + + //public static C MetricSelect(this C? collection, Func? selector) + // where C : IMetricCollection, new() where U : class, IMetric, 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 MetricSelect(this IMetricCollection collection, Func selector) + // where U : class, IMetric, 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 MetricSelect(this IMetricCollection? collection, Func? selector) + // where Ux : class, IMetric, new() where Uz : class, IMetric, new() + //{ + // if (collection is not null && selector is not null) + // { + // var destCollection = collection.CreateByInstance(collection.Count); + // for (int i = 0; i < collection.Count; i++) + // destCollection[i] = selector(collection[i]); + // return destCollection; + // } + // return null!; + //} + //public static MetricCollection MetricSelect(this MetricCollection? collection, Func? selector) + // where Ux : class, IMetric, new() where Uz : class, IMetric, new() + //{ + // if (collection is not null && selector is not null) + // { + // var destCollection = collection.CreateByInstance(collection.Count()); + // for (int i = 0; i < collection.Count(); i++) + // destCollection[i] = selector(collection[i]); + // return destCollection; + // } + // return null!; + //} + //public static MetricArray MetricSelect(this MetricArray? collection, Func? selector) + // where Ux : class, IMetric, new() where Uz : class, IMetric, new() + //{ + // var coll = collection?.ToArray(); + // if (coll is not null && selector is not null) + // { + // var destCollection = new MetricArray(coll.Length); + // for (int i = 0; i < coll.Length; i++) + // destCollection[i] = selector(coll[i]); + // return destCollection; + // } + // return null!; + //} + //public static MetricList MetricSelect(this MetricList? collection, Func? selector) + // where Ux : class, IMetric, new() where Uz : class, IMetric, new() + //{ + // if (collection is not null && selector is not null) + // { + // var destCollection = new MetricList(collection.Count); + // for (int i = 0; i < collection.Count; i++) + // destCollection[i] = selector(collection[i]); + // return destCollection; + // } + // return null!; + //} + //public static MetricObservableCollection MetricSelect(this MetricObservableCollection? collection, Func? selector) + // where Ux : class, IMetric, new() where Uz : class, IMetric, new() + //{ + // if (collection is not null && selector is not null) + // { + // var destCollection = new MetricObservableCollection(collection.Count); + // for (int i = 0; i < collection.Count; i++) + // destCollection[i] = selector(collection[i]); + // return destCollection; + // } + // return null!; + //} + + + //public static double[] MetricSelect(this MetricArray collection, Func selector) + // where U : class, IMetric, 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 MetricSelect(this MetricList collection, Func selector) + // where U : class, IMetric, new() + //{ + // var coll = collection ?? []; + // var list = new List(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 MetricSelect(this MetricObservableCollection collection, Func selector) + // where U : class, IMetric, new() + //{ + // var coll = collection ?? []; + // var list = new List(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(this C? collection, Func? Set) + // where C : IMetricCollection, new() where U : class, IMetric, 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(this U? T1, U? T2) where U : class, IMetric, new() => (T1.Protect_Value() < T2.Protect_Value() ? T1 : T2).Protect(); + //public static U Min(this U T1, IEnumerable units) where U : class, IMetric, new() => (T1 ?? new()).Min((units ?? []).MaxBy(u => u.Protect_Value())); + + //public static U Max(this U? T1, U? T2) where U : class, IMetric, new() => (T1.Protect_Value() > T2.Protect_Value() ? T1 : T2).Protect(); + //public static U Max(this U T1, IEnumerable units) where U : class, IMetric, 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(this N number) where N : INumber => Convert.ToDouble(number); + ////internal static double ToDouble(this N? number) where N : struct, INumber => number is not null ? Convert.ToDouble(number) : 0d; + + + //internal static IEnumerable MetricSelect(this double[] nums, Func selector) where U : class, IMetric, new() => nums.Select(selector); + //internal static IEnumerable MetricSelect(this double?[] nums, Func selector) where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + //internal static IEnumerable MetricSelect(this N[] nums, Func selector) where N : INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + //internal static IEnumerable MetricSelect(this N?[] nums, Func selector) where N : struct, INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + + + //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where U : class, IMetric, new() => nums.Select(selector); + //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where N : INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + //internal static IEnumerable MetricSelect(this IEnumerable nums, Func selector) where N : struct, INumber where U : class, IMetric, new() => nums.Select(num => selector(num.ToDouble())); + + + + + //public static U Clone(this U? metric) where U : class, IMetric, new() => new() { _Value = metric.Protect_Value() }; + //public static U Abs(this U? metric) where U : class, IMetric, new() => new() { _Value = Math.Abs(metric.Protect_Value()) }; + + ///// C^2 = A^2 + B^2 + ///// C = (A^2 + B^2).Sqrt(2) + //public static U Hypotenuse(this U? A, U? B) where U : class, IMetric, new() + //{ + // var a = A.Protect_Value(); + // var b = B.Protect_Value(); + // return new U() { _Value = Math.Sqrt(a * a + b * b) }; + //} + ///// C^2 = A^2 + B^2 + ///// B = (C^2 - A^2).Sqrt(2) + //public static U KatetFromHyp(this U? A, U? C) where U : class, IMetric, new() + //{ + // var a = A.Protect_Value(); + // var c = C.Protect_Value(); + // return new U() { _Value = Math.Sqrt(c * c - a * a) }; + //} + ///// C^2 = A^2 + B^2 + ///// B = (C^2 - A^2).Sqrt(2) + //public static U KatetFromKatet(this U? C, U? A) where U : class, IMetric, 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(this IEnumerable args) where U : IMetric, new() => new() { _Value = args?.Where(t => t is not null).Sum(m => m.Protect_Value()) ?? 0d }; + //public static U MetricAverage(this IEnumerable args) where U : IMetric, new() => new() { _Value = args?.Average(m => m.Protect_Value()) ?? double.NaN }; + //public static U MetricMax(this IEnumerable args) where U : IMetric, new() => new() { _Value = args.Max(m => m.Protect_Value()) }; + //public static U MetricMin(this IEnumerable args) where U : IMetric, new() => new() { _Value = args.Min(m => m.Protect_Value()) }; + + + + //public static C MetricSum(this IEnumerable> collections) + // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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(this IEnumerable> collections) + // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, new() + // => collections.Sum() / collections.Count(); + + + + //public static U MetricSumBy(this IEnumerable source, Func selector) + // where U : class, IMetric, 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(this IEnumerable source, Func selector) + // where U : class, IMetric, 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(this IEnumerable source, Func> selector) + // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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(this IEnumerable source, Func> selector) + // where C : MetricCollection, ICreateByCapacity, new() where U : class, IMetric, 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 ToMetricArray(this IEnumerable source) where U : class, IMetric, new() => new(source); + //public static MetricList ToMetricList(this IEnumerable source) where U : class, IMetric, new() => new(source); +} \ No newline at end of file diff --git a/QWERTYkez.Mensura/UnitJsonConverter.cs b/QWERTYkez.Mensura/UnitJsonConverter.cs new file mode 100644 index 0000000..7c4e966 --- /dev/null +++ b/QWERTYkez.Mensura/UnitJsonConverter.cs @@ -0,0 +1,56 @@ +using System.Globalization; + +namespace QWERTYkez.Mensura; + +public class UnitJsonConverter : JsonConverter where T : struct, IMensuraUnit, IEquatable +{ + // Используем инвариантную культуру, чтобы разделителем всегда была точка (10.5, а не 10,5) + private static readonly CultureInfo Culture = CultureInfo.InvariantCulture; + + public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + double double_Value; + + if (reader.TokenType == JsonTokenType.String) + { + // Безопасно парсим double из строки с поддержкой точки как разделителя + if (!double.TryParse(reader.GetString(), NumberStyles.Float, Culture, out double_Value)) + { + throw new JsonException($"Не удалось преобразовать строковое значение в double для метрики {nameof(T)}."); + } + } + else + { + // Прямое быстрое чтение числа из JSON + double_Value = reader.GetDouble(); + } + + return double_Value.ToUnit(); + } + + public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + // Записываем число напрямую в байтовый буфер без выделения памяти под строки + writer.WriteNumberValue(value.ToDouble()); + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, T value, JsonSerializerOptions options) + { + // Ключи JSON-объектов всегда должны быть строками. + // Форматируем double в строку с точкой, чтобы другие сервисы экосистемы прочитали её корректно. + // Формат "R" (Round-trip) гарантирует, что число не потеряет точность при обратном парсинге. + writer.WritePropertyName(value.ToDouble().ToString("R", Culture)); + } + + public override T ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string propertyName = reader.GetString()!; + + if (!double.TryParse(propertyName, NumberStyles.Float, Culture, out double double_Value)) + { + throw new JsonException($"Невалидное числовое значение в ключе свойства JSON: '{propertyName}' для метрики {nameof(T)}."); + } + + return double_Value.ToUnit(); + } +} diff --git a/QWERTYkez.Mensura/Units/Length.cs b/QWERTYkez.Mensura/Units/Length.cs index ce51978..bf54d18 100644 --- a/QWERTYkez.Mensura/Units/Length.cs +++ b/QWERTYkez.Mensura/Units/Length.cs @@ -11,7 +11,8 @@ public readonly partial record struct Length public static Length CentiMeter { get; } = new(LengthConv.CentiMeters.To(1)); - [NotMapped, JsonIgnore] public double CentiMeters + [NotMapped, JsonIgnore] + public double CentiMeters { get => LengthConv.CentiMeters.From(_Value); init @@ -29,21 +30,24 @@ public readonly partial record struct Length } public static Length DeciMeter { get; } = new(LengthConv.DeciMeters.To(1)); - [NotMapped, JsonIgnore] public double DeciMeters + [NotMapped, JsonIgnore] + public double DeciMeters { get => LengthConv.DeciMeters.From(_Value); init => _Value = LengthConv.DeciMeters.To(value); } public static Length Meter { get; } = new(LengthConv.Meters.To(1)); - [NotMapped, JsonIgnore] public double Meters + [NotMapped, JsonIgnore] + public double Meters { get => LengthConv.Meters.From(_Value); init => _Value = LengthConv.Meters.To(value); } public static Length KiloMeter { get; } = new(LengthConv.KiloMeters.To(1)); - [NotMapped, JsonIgnore] public double KiloMeters + [NotMapped, JsonIgnore] + public double KiloMeters { get => LengthConv.KiloMeters.From(_Value); init => _Value = LengthConv.KiloMeters.To(value); @@ -55,6 +59,39 @@ public readonly partial record struct Length public Length AddDeciMeters(double value) => new(_Value + LengthConv.DeciMeters.To(value)); public Length AddMeters(double value) => new(_Value + LengthConv.Meters.To(value)); public Length AddKiloMeters(double value) => new(_Value + LengthConv.KiloMeters.To(value)); + + + + + /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) + public Length HypFromLeg(Length B) => new(Math.Sqrt(_Value * _Value + B._Value * B._Value)); + + /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) + public Length HypFromLeg(Length? B) + { + double b = B is null ? 0d : B.Value._Value; + return new(Math.Sqrt(_Value * _Value + b * b)); + } + + /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) + public Length LegFromHyp(Length C) => new(Math.Sqrt(C._Value * C._Value - _Value * _Value)); + + /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) + public Length LegFromHyp(Length? C) + { + double c = C is null ? 0d : C.Value._Value; + return new(Math.Sqrt(c * c - _Value * _Value)); + } + + /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) + public Length LegFromLeg(Length A) => new(Math.Sqrt(_Value * _Value - A._Value * A._Value)); + + /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) + public Length LegFromLeg(Length? A) + { + double a = A is null ? 0d : A.Value._Value; + return new(Math.Sqrt(_Value * _Value - a * a)); + } } diff --git a/QWERTYkez.Mensura/Units/Pogon/PogonXXXXXXXX.Gen.cs b/QWERTYkez.Mensura/Units/Pogon/PogonXXXXXXXX.Gen.cs index c335f15..6cd6c11 100644 --- a/QWERTYkez.Mensura/Units/Pogon/PogonXXXXXXXX.Gen.cs +++ b/QWERTYkez.Mensura/Units/Pogon/PogonXXXXXXXX.Gen.cs @@ -83,7 +83,7 @@ namespace QWERTYkez.Mensura.Units result[i] = val * right[i]; return result; } - public static Span operator *(ReadOnlySpan left, Length right) + public static Span operator *(this ReadOnlySpan left, Length right) { int len = left.Length; if (len == 0) return []; @@ -93,7 +93,7 @@ namespace QWERTYkez.Mensura.Units result[i] = right * left[i]; return result; } - public static Span operator *(ReadOnlySpan left, Length? right) + public static Span operator *(this ReadOnlySpan left, Length? right) { int len = left.Length; if (len == 0) return []; @@ -277,7 +277,7 @@ namespace QWERTYkez.Mensura.Units.Pogon result[i] = val * right[i]; return result; } - public static Span operator *(ReadOnlySpan left, PogonMass right) + public static Span operator *(this ReadOnlySpan left, PogonMass right) { int len = left.Length; if (len == 0) return []; @@ -287,7 +287,7 @@ namespace QWERTYkez.Mensura.Units.Pogon result[i] = right * left[i]; return result; } - public static Span operator *(ReadOnlySpan left, PogonMass? right) + public static Span operator *(this ReadOnlySpan left, PogonMass? right) { int len = left.Length; if (len == 0) return []; diff --git a/QWERTYkez.Mensura/Units/XXXXXXXX.Gen.cs b/QWERTYkez.Mensura/Units/XXXXXXXX.Gen.cs index 52077f0..187e1d0 100644 --- a/QWERTYkez.Mensura/Units/XXXXXXXX.Gen.cs +++ b/QWERTYkez.Mensura/Units/XXXXXXXX.Gen.cs @@ -1,13 +1,11 @@ #if DEBUG global using XXXXXXXXXXXXXX = QWERTYkez.Mensura.Units.XXXXXXXXXXXXXX; -global using XXXXXXXXXXXXXXConverter = QWERTYkez.Mensura.Units.XXXXXXXXXXXXXXConverter; -global using XXXXXXXXXXXXXXExtensions = QWERTYkez.Mensura.Units.XXXXXXXXXXXXXXExtension; -using System.Globalization; -using System.Runtime.Intrinsics; using System.Runtime.Serialization; namespace QWERTYkez.Mensura.Units; +public class XXXXXXXXXXXXXXConverter : UnitJsonConverter { } + [JsonConverter(typeof(XXXXXXXXXXXXXXConverter))] public readonly partial record struct XXXXXXXXXXXXXX : IMensuraUnit, IEquatable, IMensuraUnit { @@ -183,1186 +181,14 @@ public readonly partial record struct XXXXXXXXXXXXXX : IMensuraUnit Unsafe.As(ref val); public static explicit operator double(XXXXXXXXXXXXXX unit) => unit._Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] public XXXXXXXXXXXXXX Abs() => new(Math.Abs(_Value)); - /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) - public XXXXXXXXXXXXXX HypFromLeg(XXXXXXXXXXXXXX B) => new(Math.Sqrt(_Value * _Value + B._Value * B._Value)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal R Pow2_Internal() where R : struct, IMensuraUnit, IEquatable => (_Value * _Value).ToUnit(); - /// C^2 = this^2 + B^2 C = (this^2 + B^2).Sqrt(2) - public XXXXXXXXXXXXXX HypFromLeg(XXXXXXXXXXXXXX? B) - { - double b = B is null ? 0d : B.Value._Value; - return new(Math.Sqrt(_Value * _Value + b * b)); - } - - /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) - public XXXXXXXXXXXXXX LegFromHyp(XXXXXXXXXXXXXX C) => new(Math.Sqrt(C._Value * C._Value - _Value * _Value)); - - /// C^2 = this^2 + B^2 B = (C^2 - this^2).Sqrt(2) - public XXXXXXXXXXXXXX LegFromHyp(XXXXXXXXXXXXXX? C) - { - double c = C is null ? 0d : C.Value._Value; - return new(Math.Sqrt(c * c - _Value * _Value)); - } - - /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) - public XXXXXXXXXXXXXX LegFromLeg(XXXXXXXXXXXXXX A) => new(Math.Sqrt(_Value * _Value - A._Value * A._Value)); - - /// this^2 = A^2 + B^2 B = (this^2 - A^2).Sqrt(2) - public XXXXXXXXXXXXXX LegFromLeg(XXXXXXXXXXXXXX? A) - { - double a = A is null ? 0d : A.Value._Value; - return new(Math.Sqrt(_Value * _Value - a * a)); - } -} - -public static class XXXXXXXXXXXXXXExtension -{ - - // ========================================== - // === MULTIPLY === - // ========================================== - - public static XXXXXXXXXXXXXX[] Multiply(this XXXXXXXXXXXXXX[] units, double multiplicator) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Multiply(units, multiplicator, result); - return result; - } - public static XXXXXXXXXXXXXX[] Multiply(this double multiplicator, XXXXXXXXXXXXXX[] units) - => units.Multiply(multiplicator); - public static XXXXXXXXXXXXXX?[] Multiply(this XXXXXXXXXXXXXX?[] units, double multiplicator) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value * multiplicator); - } - return result; - } - public static XXXXXXXXXXXXXX?[] Multiply(this double multiplicator, XXXXXXXXXXXXXX?[] units) - => units.Multiply(multiplicator); - - // === ReadOnlySpan === - public static Span Multiply(this ReadOnlySpan units, double multiplicator) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Multiply(units, multiplicator, result); - return result; - } - public static Span Multiply(this double multiplicator, ReadOnlySpan units) - => units.Multiply(multiplicator); - public static Span Multiply(this ReadOnlySpan units, double multiplicator) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value * multiplicator); - } - return result; - } - public static Span Multiply(this double multiplicator, ReadOnlySpan units) - => units.Multiply(multiplicator); - - // === List === - public static List Multiply(this List units, double multiplicator) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Multiply(CollectionsMarshal.AsSpan(units), multiplicator, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Multiply(this double multiplicator, List units) - => units.Multiply(multiplicator); - public static List Multiply(this List units, double multiplicator) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value * multiplicator); - } - return result; - } - public static List Multiply(this double multiplicator, List units) - => units.Multiply(multiplicator); - - // === ICollection === - public static Tcoll Multiply(this ICollection units, double multiplicator) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item._Value * multiplicator)); - return tColl; - } - public static Tcoll Multiply(this double multiplicator, ICollection units) - where Tcoll : class, ICollection, new() => Multiply(units, multiplicator); - public static Tcoll Multiply(this ICollection units, double multiplicator) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? item.Value._Value * multiplicator : 0d)); - return tColl; - } - public static Tcoll Multiply(this double multiplicator, ICollection units) - where Tcoll : class, ICollection, new() => Multiply(units, multiplicator); - - // === IEnumerable === - public static IEnumerable Multiply(this IEnumerable units, double multiplicator) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(item._Value * multiplicator); - } - public static IEnumerable Multiply(this double multiplicator, IEnumerable units) - => units.Multiply(multiplicator); - public static IEnumerable Multiply(this IEnumerable units, double multiplicator) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(item.Value._Value * multiplicator) : null; - } - public static IEnumerable Multiply(this double multiplicator, IEnumerable units) - => units.Multiply(multiplicator); - - // ========================================== - // === DIVIDE === - // ========================================== - - public static XXXXXXXXXXXXXX[] Divide(this XXXXXXXXXXXXXX[] units, double divisor) - { - if (units is null) return null!; - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) - throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Divide(units, divisor, result); - return result; - } - public static XXXXXXXXXXXXXX[] Divide(this double dividend, XXXXXXXXXXXXXX[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Divide(dividend, units, result); // Вызов зеркального ядра (число / массив) - return result; - } - public static XXXXXXXXXXXXXX?[] Divide(this XXXXXXXXXXXXXX?[] units, double divisor) - { - if (units is null) return null!; - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value / divisor); - } - return result; - } - public static XXXXXXXXXXXXXX?[] Divide(this double dividend, XXXXXXXXXXXXXX?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(dividend / item.Value._Value); - } - return result; - } - - // === ReadOnlySpan === - public static Span Divide(this ReadOnlySpan units, double divisor) - { - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Divide(units, divisor, result); - return result; - } - public static Span Divide(this double dividend, ReadOnlySpan units) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Divide(dividend, units, result); - return result; - } - public static Span Divide(this ReadOnlySpan units, double divisor) - { - if (units.Length == 0) return []; - if (divisor == 0 || double.IsNaN(divisor)) - throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value / divisor); - } - return result; - } - public static Span Divide(this double dividend, ReadOnlySpan units) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(dividend / item.Value._Value); - } - return result; - } - - // === List === - public static List Divide(this List units, double divisor) - { - if (units is null) return null!; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Divide(CollectionsMarshal.AsSpan(units), divisor, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Divide(this double dividend, List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Divide(dividend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Divide(this List units, double divisor) - { - if (units is null) return null!; - if (divisor == 0 || double.IsNaN(divisor)) throw new ArgumentException("Делитель не может быть 0 или NaN.", nameof(divisor)); - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value / divisor); - } - return result; - } - public static List Divide(this double dividend, List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(dividend / item.Value._Value); - } - return result; - } - - // === ICollection === - public static Tcoll Divide(this ICollection units, double divisor) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item._Value / divisor)); - return tColl; - } - public static Tcoll Divide(this double dividend, ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(dividend / item._Value)); - return tColl; - } - public static Tcoll Divide(this ICollection units, double divisor) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? item.Value._Value / divisor : 0d)); - return tColl; - } - public static Tcoll Divide(this double dividend, ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? dividend / item.Value._Value : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable Divide(this IEnumerable units, double divisor) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(item._Value * divisor); - } - public static IEnumerable Divide(this double dividend, IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(dividend / item._Value); - } - public static IEnumerable Divide(this IEnumerable units, double divisor) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(item.Value._Value * divisor) : null; - } - public static IEnumerable Divide(this double dividend, IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(dividend / item.Value._Value) : null; - } - - // ========================================== - // === PLUS === - // ========================================== - - public static XXXXXXXXXXXXXX[] Plus(this XXXXXXXXXXXXXX[] units, double summand) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Plus(units, summand, result); - return result; - } - public static XXXXXXXXXXXXXX[] Plus(this double summand, XXXXXXXXXXXXXX[] units) - => units.Plus(summand); - public static XXXXXXXXXXXXXX?[] Plus(this XXXXXXXXXXXXXX?[] units, double summand) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value + summand); - } - return result; - } - public static XXXXXXXXXXXXXX?[] Plus(this double summand, XXXXXXXXXXXXXX?[] units) - => units.Plus(summand); - - // === ReadOnlySpan === - public static Span Plus(this ReadOnlySpan units, double summand) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Plus(units, summand, result); - return result; - } - public static Span Plus(this double summand, ReadOnlySpan units) - => units.Plus(summand); - public static Span Plus(this ReadOnlySpan units, double summand) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value + summand); - } - return result; - } - public static Span Plus(this double summand, ReadOnlySpan units) - => units.Multiply(summand); - - // === List === - public static List Plus(this List units, double summand) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Plus(CollectionsMarshal.AsSpan(units), summand, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Plus(this double summand, List units) - => units.Plus(summand); - public static List Plus(this List units, double summand) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value + summand); - } - return result; - } - public static List Plus(this double summand, List units) - => units.Plus(summand); - - // === ICollection === - public static Tcoll Plus(this ICollection units, double summand) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item._Value + summand)); - return tColl; - } - public static Tcoll Plus(this double summand, ICollection units) - where Tcoll : class, ICollection, new() => Plus(units, summand); - public static Tcoll Plus(this ICollection units, double summand) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? item.Value._Value + summand : 0d)); - return tColl; - } - public static Tcoll Plus(this double summand, ICollection units) - where Tcoll : class, ICollection, new() => Plus(units, summand); - - // === IEnumerable === - public static IEnumerable Plus(this IEnumerable units, double summand) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(item._Value + summand); - } - public static IEnumerable Plus(this double summand, IEnumerable units) - => units.Plus(summand); - public static IEnumerable Plus(this IEnumerable units, double summand) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(item.Value._Value + summand) : null; - } - public static IEnumerable Plus(this double summand, IEnumerable units) - => units.Plus(summand); - - // ========================================== - // === MINUS === - // ========================================== - - public static XXXXXXXXXXXXXX[] Minus(this XXXXXXXXXXXXXX[] units, double subtrahend) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Minus(units, subtrahend, result); - return result; - } - public static XXXXXXXXXXXXXX[] Minus(this double minuend, XXXXXXXXXXXXXX[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Minus(minuend, units, result); // Вызов зеркального ядра (число - массив) - return result; - } - public static XXXXXXXXXXXXXX?[] Minus(this XXXXXXXXXXXXXX?[] units, double subtrahend) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value - subtrahend); - } - return result; - } - public static XXXXXXXXXXXXXX?[] Minus(this double minuend, XXXXXXXXXXXXXX?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(minuend - item.Value._Value); - } - return result; - } - - // === ReadOnlySpan === - public static Span Minus(this ReadOnlySpan units, double subtrahend) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Minus(units, subtrahend, result); - return result; - } - public static Span Minus(this double minuend, ReadOnlySpan units) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Minus(minuend, units, result); - return result; - } - public static Span Minus(this ReadOnlySpan units, double subtrahend) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value - subtrahend); - } - return result; - } - public static Span Minus(this double minuend, ReadOnlySpan units) - { - if (units.Length == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(minuend - item.Value._Value); - } - return result; - } - - // === List === - public static List Minus(this List units, double subtrahend) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Minus(CollectionsMarshal.AsSpan(units), subtrahend, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Minus(this double minuend, List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Minus(minuend, CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Minus(this List units, double subtrahend) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value - subtrahend); - } - return result; - } - public static List Minus(this double minuend, List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(minuend - item.Value._Value); - } - return result; - } - - // === ICollection === - public static Tcoll Minus(this ICollection units, double subtrahend) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item._Value - subtrahend)); - return tColl; - } - public static Tcoll Minus(this double minuend, ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(minuend - item._Value)); - return tColl; - } - public static Tcoll Minus(this ICollection units, double subtrahend) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? item.Value._Value - subtrahend : 0d)); - return tColl; - } - public static Tcoll Minus(this double minuend, ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? minuend - item.Value._Value : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable Minus(this IEnumerable units, double subtrahend) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(item._Value - subtrahend); - } - public static IEnumerable Minus(this double minuend, IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(minuend - item._Value); - } - public static IEnumerable Minus(this IEnumerable units, double subtrahend) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(item.Value._Value - subtrahend) : null; - } - public static IEnumerable Minus(this double minuend, IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(minuend - item.Value._Value) : null; - } - - // ========================================== - // ЦЕЛАЯ СТЕПЕНЬ (int power) - // ========================================== - - public static XXXXXXXXXXXXXX[] Pow(this XXXXXXXXXXXXXX[] units, int power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Pow(units, power, result); - return result; - } - public static XXXXXXXXXXXXXX?[] Pow(this XXXXXXXXXXXXXX?[] units, int power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - // Используем тот же быстрый QuickPow, что и в ядре, чтобы не вызывать тяжелый Math.Pow - item = new XXXXXXXXXXXXXX(item.Value._Value.QuickPow(power)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span Pow(this ReadOnlySpan units, int power) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Pow(units, power, result); - return result; - } - public static Span Pow(this ReadOnlySpan units, int power) - { - int len = units.Length; - if (len == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value.QuickPow(power)); - } - return result; - } - - // === List === - public static List Pow(this List units, int power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Pow(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Pow(this List units, int power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(item.Value._Value.QuickPow(power)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection units, int power) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item._Value.QuickPow(power))); - return tColl; - } - public static Tcoll Pow(this ICollection units, int power) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? item.Value._Value.QuickPow(power) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable Pow(this IEnumerable units, int power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(item._Value.QuickPow(power)); - } - public static IEnumerable Pow(this IEnumerable units, int power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(item.Value._Value.QuickPow(power)) : null; - } - - // ========================================== - // ДРОБНАЯ СТЕПЕНЬ (double power) - // ========================================== - - public static XXXXXXXXXXXXXX[] Pow(this XXXXXXXXXXXXXX[] units, double power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.PowCore(units, power, result); - return result; - } - public static XXXXXXXXXXXXXX?[] Pow(this XXXXXXXXXXXXXX?[] units, double power) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - item = new XXXXXXXXXXXXXX(Math.Pow(item.Value._Value, power)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span Pow(this ReadOnlySpan units, double power) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.PowCore(units, power, result); - return result; - } - public static Span Pow(this ReadOnlySpan units, double power) - { - int len = units.Length; - if (len == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(Math.Pow(item.Value._Value, power)); - } - return result; - } - - // === List === - public static List Pow(this List units, double power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.PowCore(CollectionsMarshal.AsSpan(units), power, CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Pow(this List units, double power) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(Math.Pow(item.Value._Value, power)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection units, double power) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(Math.Pow(item._Value, power))); - return tColl; - } - public static Tcoll Pow(this ICollection units, double power) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? Math.Pow(item.Value._Value, power) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable Pow(this IEnumerable units, double power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(Math.Pow(item._Value, power)); - } - public static IEnumerable Pow(this IEnumerable units, double power) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(Math.Pow(item.Value._Value, power)) : null; - } - - - // ========================================== - // КВАДРАТНЫЙ КОРЕНЬ (Sqrt) - // ========================================== - - public static XXXXXXXXXXXXXX[] Sqrt(this XXXXXXXXXXXXXX[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Sqrt(units, result); - return result; - } - public static XXXXXXXXXXXXXX?[] Sqrt(this XXXXXXXXXXXXXX?[] units) - { - if (units is null) return null!; - if (units.Length == 0) return []; - - var result = (XXXXXXXXXXXXXX?[])units.Clone(); - int len = result.Length; - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) - { - item = new XXXXXXXXXXXXXX(Math.Sqrt(item.Value._Value)); - } - } - return result; - } - - // === ReadOnlySpan === - public static Span Sqrt(this ReadOnlySpan units) - { - if (units.Length == 0) return []; - var result = new XXXXXXXXXXXXXX[units.Length]; - Extensions.Sqrt(units, result); - return result; - } - public static Span Sqrt(this ReadOnlySpan units) - { - int len = units.Length; - if (len == 0) return []; - Span result = new XXXXXXXXXXXXXX?[units.Length]; - units.CopyTo(result); - for (int i = 0; i < len; i++) - { - ref var item = ref result[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(Math.Sqrt(item.Value._Value)); - } - return result; - } - - // === List === - public static List Sqrt(this List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(len); - result.SetCountUnsafe(len); - Extensions.Sqrt(CollectionsMarshal.AsSpan(units), CollectionsMarshal.AsSpan(result)); - return result; - } - public static List Sqrt(this List units) - { - if (units is null) return null!; - int len = units.Count; - if (len == 0) return []; - - var result = new List(units); - Span dstSpan = CollectionsMarshal.AsSpan(result); - for (int i = 0; i < len; i++) - { - ref var item = ref dstSpan[i]; - if (item.HasValue) item = new XXXXXXXXXXXXXX(Math.Sqrt(item.Value._Value)); - } - return result; - } - - // === ICollection === - public static Tcoll Pow(this ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(Math.Sqrt(item._Value))); - return tColl; - } - public static Tcoll Pow(this ICollection units) - where Tcoll : class, ICollection, new() - { - if (units is null) return null!; - var tColl = new Tcoll(); - if (tColl is List list) - list.Capacity = units.Count; - foreach (var item in units) - tColl.Add(new XXXXXXXXXXXXXX(item.HasValue ? Math.Sqrt(item.Value._Value) : 0d)); - return tColl; - } - - // === IEnumerable === - public static IEnumerable Pow(this IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return new XXXXXXXXXXXXXX(Math.Sqrt(item._Value)); - } - public static IEnumerable Pow(this IEnumerable units) - { - ArgumentNullException.ThrowIfNull(units); - foreach (var item in units) - yield return item.HasValue - ? new XXXXXXXXXXXXXX(Math.Sqrt(item.Value._Value)) : null; - } -} - -public class XXXXXXXXXXXXXXConverter : JsonConverter -{ - // Используем инвариантную культуру, чтобы разделителем всегда была точка (10.5, а не 10,5) - private static readonly CultureInfo Culture = CultureInfo.InvariantCulture; - - public override XXXXXXXXXXXXXX Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - double double_Value; - - if (reader.TokenType == JsonTokenType.String) - { - // Безопасно парсим double из строки с поддержкой точки как разделителя - if (!double.TryParse(reader.GetString(), NumberStyles.Float, Culture, out double_Value)) - { - throw new JsonException($"Не удалось преобразовать строковое значение в double для метрики {nameof(XXXXXXXXXXXXXX)}."); - } - } - else - { - // Прямое быстрое чтение числа из JSON - double_Value = reader.GetDouble(); - } - - return new(double_Value); - } - - public override void Write(Utf8JsonWriter writer, XXXXXXXXXXXXXX value, JsonSerializerOptions options) - { - // Записываем число напрямую в байтовый буфер без выделения памяти под строки - writer.WriteNumberValue(value._Value); - } - - public override void WriteAsPropertyName(Utf8JsonWriter writer, XXXXXXXXXXXXXX value, JsonSerializerOptions options) - { - // Ключи JSON-объектов всегда должны быть строками. - // Форматируем double в строку с точкой, чтобы другие сервисы экосистемы прочитали её корректно. - // Формат "R" (Round-trip) гарантирует, что число не потеряет точность при обратном парсинге. - writer.WritePropertyName(value._Value.ToString("R", Culture)); - } - - public override XXXXXXXXXXXXXX ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - string propertyName = reader.GetString()!; - - if (!double.TryParse(propertyName, NumberStyles.Float, Culture, out double double_Value)) - { - throw new JsonException($"Невалидное числовое значение в ключе свойства JSON: '{propertyName}' для метрики {nameof(XXXXXXXXXXXXXX)}."); - } - - return new(double_Value); - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal R Sqrt_Internal() where R : struct, IMensuraUnit, IEquatable => Math.Sqrt(_Value).ToUnit(); } #endif \ No newline at end of file diff --git a/QWERTYkez.Mensura/globals.cs b/QWERTYkez.Mensura/globals.cs index 37c9a56..27b3634 100644 --- a/QWERTYkez.Mensura/globals.cs +++ b/QWERTYkez.Mensura/globals.cs @@ -11,4 +11,5 @@ global using System.Runtime.InteropServices; global using System.Text.Json; global using System.Text.Json.Serialization; global using QWERTYkez.Mensura; +global using QWERTYkez.Mensura.Extensions; global using QWERTYkez.Mensura.Units; \ No newline at end of file