diff --git a/X10D.Tests/src/IO/UInt16Tests.cs b/X10D.Tests/src/IO/UInt16Tests.cs index 465a1e0..be9360e 100644 --- a/X10D.Tests/src/IO/UInt16Tests.cs +++ b/X10D.Tests/src/IO/UInt16Tests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using X10D.IO; namespace X10D.Tests.IO; diff --git a/X10D.Tests/src/IO/UInt32Tests.cs b/X10D.Tests/src/IO/UInt32Tests.cs index cb0f17b..7847759 100644 --- a/X10D.Tests/src/IO/UInt32Tests.cs +++ b/X10D.Tests/src/IO/UInt32Tests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using X10D.IO; namespace X10D.Tests.IO; diff --git a/X10D.Tests/src/IO/UInt64Tests.cs b/X10D.Tests/src/IO/UInt64Tests.cs index dfaa621..e783e2a 100644 --- a/X10D.Tests/src/IO/UInt64Tests.cs +++ b/X10D.Tests/src/IO/UInt64Tests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using X10D.IO; namespace X10D.Tests.IO; diff --git a/X10D.Tests/src/Math/UInt32Tests.cs b/X10D.Tests/src/Math/UInt32Tests.cs index 5c161ff..f2db8e8 100644 --- a/X10D.Tests/src/Math/UInt32Tests.cs +++ b/X10D.Tests/src/Math/UInt32Tests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using X10D.Math; namespace X10D.Tests.Math; diff --git a/X10D.Tests/src/Math/UInt64Tests.cs b/X10D.Tests/src/Math/UInt64Tests.cs index 909548f..7391cc5 100644 --- a/X10D.Tests/src/Math/UInt64Tests.cs +++ b/X10D.Tests/src/Math/UInt64Tests.cs @@ -1,4 +1,4 @@ -using NUnit.Framework; +using NUnit.Framework; using X10D.Math; namespace X10D.Tests.Math; diff --git a/X10D/src/Collections/BinaryIntegerExtensions.cs b/X10D/src/Collections/BinaryIntegerExtensions.cs new file mode 100644 index 0000000..7c68d88 --- /dev/null +++ b/X10D/src/Collections/BinaryIntegerExtensions.cs @@ -0,0 +1,89 @@ +#if NET7_0_OR_GREATER +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.Intrinsics.X86; +using X10D.CompilerServices; + +namespace X10D.Collections; + +/// +/// Collection-related extension methods for . +/// +public static class BinaryIntegerExtensions +{ + /// + /// Unpacks this integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// An array of with a length equal to the size of . + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static bool[] Unpack(this TInteger value) + where TInteger : unmanaged, IBinaryInteger + { + unsafe + { + var buffer = new bool[sizeof(TInteger) * 8]; + value.Unpack(buffer); + return buffer; + } + } + + /// + /// Unpacks this integer into a boolean list, treating it as a bit field. + /// + /// The value to unpack. + /// When this method returns, contains the unpacked booleans from . + /// is not large enough to contain the result. + [MethodImpl(CompilerResources.MaxOptimization)] + public static void Unpack(this TInteger value, Span destination) + where TInteger : unmanaged, IBinaryInteger + { + unsafe + { + if (destination.Length < sizeof(TInteger) * 8) + { + throw new ArgumentException(ExceptionMessages.DestinationSpanLengthTooShort, nameof(destination)); + } + } + + switch (value) + { + case byte valueByte when Sse3.IsSupported: + valueByte.UnpackInternal_Ssse3(destination); + break; + + case int valueInt32 when Avx2.IsSupported: + valueInt32.UnpackInternal_Ssse3(destination); + break; + + case int valueInt32 when Sse3.IsSupported: + valueInt32.UnpackInternal_Ssse3(destination); + break; + + case short valueInt16 when Sse3.IsSupported: + valueInt16.UnpackInternal_Ssse3(destination); + break; + + default: + UnpackInternal_Fallback(value, destination); + break; + } + } + + [MethodImpl(CompilerResources.MaxOptimization)] + internal static void UnpackInternal_Fallback(this TInteger value, Span destination) + where TInteger : unmanaged, IBinaryInteger + { + unsafe + { + int bitCount = sizeof(TInteger) * 8; + for (var index = 0; index < bitCount; index++) + { + destination[index] = (value & (TInteger.One << index)) != TInteger.Zero; + } + } + } +} +#endif diff --git a/X10D/src/Collections/ByteExtensions.cs b/X10D/src/Collections/ByteExtensions.cs index 5207b26..bdcdcce 100644 --- a/X10D/src/Collections/ByteExtensions.cs +++ b/X10D/src/Collections/ByteExtensions.cs @@ -1,5 +1,7 @@ +#if !NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +#endif using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -14,6 +16,7 @@ public static class ByteExtensions { private const int Size = sizeof(byte) * 8; +#if !NET7_0_OR_GREATER /// /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. /// @@ -51,6 +54,7 @@ public static class ByteExtensions UnpackInternal_Fallback(value, destination); } +#endif [MethodImpl(CompilerResources.MaxOptimization)] internal static void UnpackInternal_Fallback(this byte value, Span destination) diff --git a/X10D/src/Collections/Int16Extensions.cs b/X10D/src/Collections/Int16Extensions.cs index fb722b9..e3e5570 100644 --- a/X10D/src/Collections/Int16Extensions.cs +++ b/X10D/src/Collections/Int16Extensions.cs @@ -1,5 +1,7 @@ +#if !NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +#endif using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -14,6 +16,7 @@ public static class Int16Extensions { private const int Size = sizeof(short) * 8; +#if !NET7_0_OR_GREATER /// /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. /// @@ -51,6 +54,7 @@ public static class Int16Extensions UnpackInternal_Fallback(value, destination); } +#endif [MethodImpl(CompilerResources.MaxOptimization)] internal static void UnpackInternal_Fallback(this short value, Span destination) diff --git a/X10D/src/Collections/Int32Extensions.cs b/X10D/src/Collections/Int32Extensions.cs index 19dbe7e..b74a311 100644 --- a/X10D/src/Collections/Int32Extensions.cs +++ b/X10D/src/Collections/Int32Extensions.cs @@ -1,5 +1,7 @@ +#if !NET7_0_OR_GREATER using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; +#endif using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; using System.Runtime.Intrinsics.X86; @@ -14,6 +16,7 @@ public static class Int32Extensions { private const int Size = sizeof(int) * 8; +#if !NET7_0_OR_GREATER /// /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. /// @@ -57,6 +60,7 @@ public static class Int32Extensions UnpackInternal_Fallback(value, destination); } +#endif [MethodImpl(CompilerResources.MaxOptimization)] internal static void UnpackInternal_Fallback(this int value, Span destination) diff --git a/X10D/src/Collections/Int64Extensions.cs b/X10D/src/Collections/Int64Extensions.cs index 505f3da..c72dfbd 100644 --- a/X10D/src/Collections/Int64Extensions.cs +++ b/X10D/src/Collections/Int64Extensions.cs @@ -1,3 +1,4 @@ +#if !NET7_0_OR_GREATER using System.Diagnostics.Contracts; namespace X10D.Collections; @@ -41,3 +42,4 @@ public static class Int64Extensions } } } +#endif diff --git a/X10D/src/IO/DoubleExtensions.cs b/X10D/src/IO/DoubleExtensions.cs index c190e45..8856c79 100644 --- a/X10D/src/IO/DoubleExtensions.cs +++ b/X10D/src/IO/DoubleExtensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; diff --git a/X10D/src/IO/Int16Extensions.cs b/X10D/src/IO/Int16Extensions.cs index f5840e2..af81b2d 100644 --- a/X10D/src/IO/Int16Extensions.cs +++ b/X10D/src/IO/Int16Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/IO/Int32Extensions.cs b/X10D/src/IO/Int32Extensions.cs index 76449f8..0556ab6 100644 --- a/X10D/src/IO/Int32Extensions.cs +++ b/X10D/src/IO/Int32Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/IO/Int64Extensions.cs b/X10D/src/IO/Int64Extensions.cs index 133f86a..447c794 100644 --- a/X10D/src/IO/Int64Extensions.cs +++ b/X10D/src/IO/Int64Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/IO/SingleExtensions.cs b/X10D/src/IO/SingleExtensions.cs index efdb17b..6e65001 100644 --- a/X10D/src/IO/SingleExtensions.cs +++ b/X10D/src/IO/SingleExtensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; #if !NET5_0_OR_GREATER using System.Runtime.InteropServices; diff --git a/X10D/src/IO/UInt16Extensions.cs b/X10D/src/IO/UInt16Extensions.cs index a977277..972281a 100644 --- a/X10D/src/IO/UInt16Extensions.cs +++ b/X10D/src/IO/UInt16Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/IO/UInt32Extensions.cs b/X10D/src/IO/UInt32Extensions.cs index 7865ff5..099a87d 100644 --- a/X10D/src/IO/UInt32Extensions.cs +++ b/X10D/src/IO/UInt32Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/IO/UInt64Extensions.cs b/X10D/src/IO/UInt64Extensions.cs index a6adf1c..bd9305c 100644 --- a/X10D/src/IO/UInt64Extensions.cs +++ b/X10D/src/IO/UInt64Extensions.cs @@ -1,4 +1,4 @@ -using System.Buffers.Binary; +using System.Buffers.Binary; using System.Diagnostics.Contracts; namespace X10D.IO; diff --git a/X10D/src/Math/BigIntegerExtensions.cs b/X10D/src/Math/BigIntegerExtensions.cs index b2ab805..79c936c 100644 --- a/X10D/src/Math/BigIntegerExtensions.cs +++ b/X10D/src/Math/BigIntegerExtensions.cs @@ -10,6 +10,7 @@ namespace X10D.Math; /// public static class BigIntegerExtensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current integer. /// @@ -42,6 +43,7 @@ public static class BigIntegerExtensions BigInteger root = BigInteger.Abs(value).Mod(9); return (int)(root == 0 ? 9 : root); } +#endif /// /// Returns the factorial of the current 64-bit signed integer. @@ -170,6 +172,7 @@ public static class BigIntegerExtensions return value * other / value.GreatestCommonFactor(other); } +#if !NET7_0_OR_GREATER /// /// Performs a modulo operation which supports a negative dividend. /// @@ -191,6 +194,7 @@ public static class BigIntegerExtensions BigInteger r = dividend % divisor; return r < 0 ? r + divisor : r; } +#endif /// /// Returns the multiplicative persistence of a specified value. diff --git a/X10D/src/Math/BinaryIntegerExtensions.cs b/X10D/src/Math/BinaryIntegerExtensions.cs new file mode 100644 index 0000000..5b460c9 --- /dev/null +++ b/X10D/src/Math/BinaryIntegerExtensions.cs @@ -0,0 +1,52 @@ +#if NET7_0_OR_GREATER +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; +using X10D.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +public static class BinaryIntegerExtensions +{ + /// + /// Returns the number of digits in the current binary integer. + /// + /// The value whose digit count to compute. + /// The number of digits in . + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static int CountDigits(this TInteger value) + where TInteger : IBinaryInteger + { + if (TInteger.IsZero(value)) + { + return 1; + } + + return 1 + (int)System.Math.Floor(System.Math.Log10(System.Math.Abs(double.CreateChecked(value)))); + } + + /// + /// Computes the digital root of this integer. + /// + /// The value whose digital root to compute. + /// The digital root of . + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// + /// The digital root is defined as the recursive sum of digits until that result is a single digit. + /// For example, the digital root of 239 is 5: 2 + 3 + 9 = 14, then 1 + 4 = 5. + /// + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static int DigitalRoot(this TInteger value) + where TInteger : IBinaryInteger + { + var nine = TInteger.CreateChecked(9); + TInteger root = TInteger.Abs(value).Mod(nine); + return int.CreateChecked(root == TInteger.Zero ? nine : root); + } +} +#endif diff --git a/X10D/src/Math/ByteExtensions.cs b/X10D/src/Math/ByteExtensions.cs index 95d1188..500988d 100644 --- a/X10D/src/Math/ByteExtensions.cs +++ b/X10D/src/Math/ByteExtensions.cs @@ -9,6 +9,7 @@ namespace X10D.Math; /// public static class ByteExtensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 8-bit unsigned integer. /// @@ -41,6 +42,7 @@ public static class ByteExtensions int root = value % 9; return (byte)(root == 0 ? 9 : root); } +#endif /// /// Returns the factorial of the current 8-bit unsigned integer. diff --git a/X10D/src/Math/Int16Extensions.cs b/X10D/src/Math/Int16Extensions.cs index aa9fa9b..dc3cf96 100644 --- a/X10D/src/Math/Int16Extensions.cs +++ b/X10D/src/Math/Int16Extensions.cs @@ -9,6 +9,7 @@ namespace X10D.Math; /// public static class Int16Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 16-bit signed integer. /// @@ -40,6 +41,7 @@ public static class Int16Extensions short root = System.Math.Abs(value).Mod(9); return root < 1 ? (short)(9 - root) : root; } +#endif /// /// Returns the factorial of the current 16-bit signed integer. @@ -140,6 +142,7 @@ public static class Int16Extensions return (short)((long)value).LowestCommonMultiple(other); } +#if !NET7_0_OR_GREATER /// /// Performs a modulo operation which supports a negative dividend. /// @@ -161,6 +164,7 @@ public static class Int16Extensions int r = dividend % divisor; return (short)(r < 0 ? r + divisor : r); } +#endif /// /// Returns the multiplicative persistence of a specified value. diff --git a/X10D/src/Math/Int32Extensions.cs b/X10D/src/Math/Int32Extensions.cs index 4d91fcd..7ed6535 100644 --- a/X10D/src/Math/Int32Extensions.cs +++ b/X10D/src/Math/Int32Extensions.cs @@ -9,6 +9,7 @@ namespace X10D.Math; /// public static class Int32Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 32-bit signed integer. /// @@ -40,6 +41,7 @@ public static class Int32Extensions int root = System.Math.Abs(value).Mod(9); return root < 1 ? 9 - root : root; } +#endif /// /// Returns the factorial of the current 32-bit signed integer. @@ -140,6 +142,7 @@ public static class Int32Extensions return (int)((long)value).LowestCommonMultiple(other); } +#if !NET7_0_OR_GREATER /// /// Performs a modulo operation which supports a negative dividend. /// @@ -161,6 +164,7 @@ public static class Int32Extensions int r = dividend % divisor; return r < 0 ? r + divisor : r; } +#endif /// /// Returns the multiplicative persistence of a specified value. diff --git a/X10D/src/Math/Int64Extensions.cs b/X10D/src/Math/Int64Extensions.cs index 0c6a530..64b94e0 100644 --- a/X10D/src/Math/Int64Extensions.cs +++ b/X10D/src/Math/Int64Extensions.cs @@ -9,6 +9,7 @@ namespace X10D.Math; /// public static class Int64Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 64-bit signed integer. /// @@ -40,6 +41,7 @@ public static class Int64Extensions long root = System.Math.Abs(value).Mod(9L); return root < 1L ? 9L - root : root; } +#endif /// /// Returns the factorial of the current 64-bit signed integer. @@ -179,6 +181,7 @@ public static class Int64Extensions return value * other / value.GreatestCommonFactor(other); } +#if !NET7_0_OR_GREATER /// /// Performs a modulo operation which supports a negative dividend. /// @@ -200,6 +203,7 @@ public static class Int64Extensions long r = dividend % divisor; return r < 0 ? r + divisor : r; } +#endif /// /// Returns the multiplicative persistence of a specified value. diff --git a/X10D/src/Math/MathUtility.cs b/X10D/src/Math/MathUtility.cs index 59601ce..0ca9365 100644 --- a/X10D/src/Math/MathUtility.cs +++ b/X10D/src/Math/MathUtility.cs @@ -1,4 +1,5 @@ using System.Diagnostics.Contracts; +using System.Numerics; using System.Runtime.CompilerServices; using X10D.CompilerServices; @@ -12,34 +13,6 @@ public static class MathUtility private const double DefaultGamma = 2.2; private const float DefaultGammaF = 2.2f; - /// - /// Applies a simple bias function to value. - /// - /// The value to which the bias function will be applied. - /// The bias value. Valid values range from 0-1. - /// The biased result. - /// - /// If is less than 0.5, will be shifted downward; otherwise, upward. - /// - public static float Bias(float value, float bias) - { - return value / ((1.0f / bias - 2.0f) * (1.0f - value) + 1.0f); - } - - /// - /// Applies a simple bias function to value. - /// - /// The value to which the bias function will be applied. - /// The bias value. Valid values range from 0-1. - /// The biased result. - /// - /// If is less than 0.5, will be shifted downward; otherwise, upward. - /// - public static double Bias(double value, double bias) - { - return value / ((1.0 / bias - 2.0) * (1.0 - value) + 1.0); - } - /// /// Calculates exponential decay for a value. /// @@ -154,6 +127,35 @@ public static class MathUtility return (alpha - start) / (end - start); } +#if !NET7_0_OR_GREATER + /// + /// Applies a simple bias function to value. + /// + /// The value to which the bias function will be applied. + /// The bias value. Valid values range from 0-1. + /// The biased result. + /// + /// If is less than 0.5, will be shifted downward; otherwise, upward. + /// + public static float Bias(float value, float bias) + { + return value / ((1.0f / bias - 2.0f) * (1.0f - value) + 1.0f); + } + + /// + /// Applies a simple bias function to value. + /// + /// The value to which the bias function will be applied. + /// The bias value. Valid values range from 0-1. + /// The biased result. + /// + /// If is less than 0.5, will be shifted downward; otherwise, upward. + /// + public static double Bias(double value, double bias) + { + return value / ((1.0 / bias - 2.0) * (1.0 - value) + 1.0); + } + /// /// Linearly interpolates from one value to a target using a specified alpha. /// @@ -190,6 +192,93 @@ public static class MathUtility return ((1.0 - alpha) * value) + (alpha * target); } + /// + /// Performs smooth Hermite interpolation from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// The interpolation result. + public static float SmoothStep(float value, float target, float alpha) + { + alpha = System.Math.Clamp(alpha, 0.0f, 1.0f); + alpha = -2.0f * alpha * alpha * alpha + 3.0f * alpha * alpha; + return target * alpha + value * (1.0f - alpha); + } + + /// + /// Performs smooth Hermite interpolation from one value to a target using a specified alpha. + /// + /// The interpolation source. + /// The interpolation target. + /// The interpolation alpha. + /// The interpolation result. + public static double SmoothStep(double value, double target, double alpha) + { + alpha = System.Math.Clamp(alpha, 0.0, 1.0); + alpha = -2.0 * alpha * alpha * alpha + 3.0 * alpha * alpha; + return target * alpha + value * (1.0 - alpha); + } + + /// + /// Converts a value from being a percentage of one range, to being the same percentage in a new range. + /// + /// The value to convert. + /// The old minimum value. + /// The old maximum value. + /// The new minimum value. + /// The new maximum value. + /// The scaled value. + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static float ScaleRange(float value, float oldMin, float oldMax, float newMin, float newMax) + { + float oldRange = oldMax - oldMin; + float newRange = newMax - newMin; + float alpha = (value - oldMin) / oldRange; + return (alpha * newRange) + newMin; + } + + /// + /// Converts a value from being a percentage of one range, to being the same percentage in a new range. + /// + /// The value to convert. + /// The old minimum value. + /// The old maximum value. + /// The new minimum value. + /// The new maximum value. + /// The scaled value. + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static double ScaleRange(double value, double oldMin, double oldMax, double newMin, double newMax) + { + double oldRange = oldMax - oldMin; + double newRange = newMax - newMin; + double alpha = (value - oldMin) / oldRange; + return (alpha * newRange) + newMin; + } + + /// + /// Returns the incremental sawtooth wave of a given value. + /// + /// The value to calculate. + /// The sawtooth wave of the given value. + public static float Sawtooth(float value) + { + return (value - MathF.Floor(value)); + } + + /// + /// Returns the incremental sawtooth wave of a given value. + /// + /// The value to calculate. + /// The sawtooth wave of the given value. + public static double Sawtooth(double value) + { + return (value - System.Math.Floor(value)); + } +#endif + /// /// Converts a linear value to a gamma-encoded value using a gamma value of 2.2. /// @@ -272,64 +361,6 @@ public static class MathUtility return Unsafe.As(ref result); } - /// - /// Returns the incremental sawtooth wave of a given value. - /// - /// The value to calculate. - /// The sawtooth wave of the given value. - public static float Sawtooth(float value) - { - return (value - MathF.Floor(value)); - } - - /// - /// Returns the incremental sawtooth wave of a given value. - /// - /// The value to calculate. - /// The sawtooth wave of the given value. - public static double Sawtooth(double value) - { - return (value - System.Math.Floor(value)); - } - - /// - /// Converts a value from being a percentage of one range, to being the same percentage in a new range. - /// - /// The value to convert. - /// The old minimum value. - /// The old maximum value. - /// The new minimum value. - /// The new maximum value. - /// The scaled value. - [Pure] - [MethodImpl(CompilerResources.MaxOptimization)] - public static float ScaleRange(float value, float oldMin, float oldMax, float newMin, float newMax) - { - float oldRange = oldMax - oldMin; - float newRange = newMax - newMin; - float alpha = (value - oldMin) / oldRange; - return (alpha * newRange) + newMin; - } - - /// - /// Converts a value from being a percentage of one range, to being the same percentage in a new range. - /// - /// The value to convert. - /// The old minimum value. - /// The old maximum value. - /// The new minimum value. - /// The new maximum value. - /// The scaled value. - [Pure] - [MethodImpl(CompilerResources.MaxOptimization)] - public static double ScaleRange(double value, double oldMin, double oldMax, double newMin, double newMax) - { - double oldRange = oldMax - oldMin; - double newRange = newMax - newMin; - double alpha = (value - oldMin) / oldRange; - return (alpha * newRange) + newMin; - } - /// /// Calculates the sigmoid function for the given input value. /// @@ -358,18 +389,71 @@ public static class MathUtility return 1.0f / (1.0f + System.Math.Exp(-value)); } +#if NET7_0_OR_GREATER /// - /// Performs smooth Hermite interpolation from one value to a target using a specified alpha. + /// Applies a simple bias function to value. + /// + /// The value to which the bias function will be applied. + /// The bias value. Valid values range from 0-1. + /// The biased result. + /// + /// If is less than 0.5, will be shifted downward; otherwise, upward. + /// + public static TNumber Bias(TNumber value, TNumber bias) + where TNumber : INumber + { + TNumber identity = TNumber.MultiplicativeIdentity; + return value / ((identity / bias - TNumber.CreateChecked(2)) * (identity - value) + identity); + } + + /// + /// Linearly interpolates from one value to a target using a specified alpha. /// /// The interpolation source. /// The interpolation target. /// The interpolation alpha. - /// The interpolation result. - public static float SmoothStep(float value, float target, float alpha) + /// + /// The interpolation result as determined by (1 - alpha) * value + alpha * target. + /// + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static TNumber Lerp(TNumber value, TNumber target, TNumber alpha) + where TNumber : INumber { - alpha = System.Math.Clamp(alpha, 0.0f, 1.0f); - alpha = -2.0f * alpha * alpha * alpha + 3.0f * alpha * alpha; - return target * alpha + value * (1.0f - alpha); + // rookie mistake: a + t * (b - a) + // "precise" method: (1 - t) * a + t * b + return ((TNumber.MultiplicativeIdentity - alpha) * value) + (alpha * target); + } + + /// + /// Returns the incremental sawtooth wave of a given value. + /// + /// The value to calculate. + /// The sawtooth wave of the given value. + public static TNumber Sawtooth(TNumber value) + where TNumber : IFloatingPoint + { + return (value - TNumber.Floor(value)); + } + + /// + /// Converts a value from being a percentage of one range, to being the same percentage in a new range. + /// + /// The value to convert. + /// The old minimum value. + /// The old maximum value. + /// The new minimum value. + /// The new maximum value. + /// The scaled value. + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static TNumber ScaleRange(TNumber value, TNumber oldMin, TNumber oldMax, TNumber newMin, TNumber newMax) + where TNumber : INumber + { + TNumber oldRange = oldMax - oldMin; + TNumber newRange = newMax - newMin; + TNumber alpha = (value - oldMin) / oldRange; + return (alpha * newRange) + newMin; } /// @@ -379,10 +463,16 @@ public static class MathUtility /// The interpolation target. /// The interpolation alpha. /// The interpolation result. - public static double SmoothStep(double value, double target, double alpha) + public static TNumber SmoothStep(TNumber value, TNumber target, TNumber alpha) + where TNumber : INumber { - alpha = System.Math.Clamp(alpha, 0.0, 1.0); - alpha = -2.0 * alpha * alpha * alpha + 3.0 * alpha * alpha; - return target * alpha + value * (1.0 - alpha); + TNumber one = TNumber.One; + TNumber two = one + one; + TNumber three = two + one; + + alpha = TNumber.Clamp(alpha, TNumber.Zero, TNumber.One); + alpha = -two * alpha * alpha * alpha + three * alpha * alpha; + return target * alpha + value * (one - alpha); } +#endif } diff --git a/X10D/src/Math/NumberExtensions.cs b/X10D/src/Math/NumberExtensions.cs new file mode 100644 index 0000000..3a058c5 --- /dev/null +++ b/X10D/src/Math/NumberExtensions.cs @@ -0,0 +1,104 @@ +#if NET7_0_OR_GREATER +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; +using X10D.CompilerServices; + +namespace X10D.Math; + +/// +/// Math-related extension methods for . +/// +public static class NumberExtensions +{ + /// + /// Returns a value indicating whether the current value is evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static bool IsEven(this TNumber value) + where TNumber : INumber + { + return value % TNumber.CreateChecked(2) == TNumber.Zero; + } + + /// + /// Returns a value indicating whether the current value is not evenly divisible by 2. + /// + /// The value whose parity to check. + /// + /// if is not evenly divisible by 2, or + /// otherwise. + /// + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static bool IsOdd(this TNumber value) + where TNumber : INumber + { + return !value.IsEven(); + } + + /// + /// Performs a modulo operation which supports a negative dividend. + /// + /// The dividend. + /// The divisor. + /// The result of dividend mod divisor. + /// + /// The % operator (commonly called the modulo operator) in C# is not defined to be modulo, but is instead + /// remainder. This quirk inherently makes it difficult to use modulo in a negative context, as x % y where x is + /// negative will return a negative value, akin to -(x % y), even if precedence is forced. This method provides a + /// modulo operation which supports negative dividends. + /// + /// ShreevatsaR, https://stackoverflow.com/a/1082938/1467293 + /// CC-BY-SA 2.5 + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static TNumber Mod(this TNumber dividend, TNumber divisor) + where TNumber : INumber + { + TNumber r = dividend % divisor; + return r < TNumber.Zero ? r + divisor : r; + } + + /// + /// Returns an integer that indicates the sign of this number. + /// + /// A signed number. + /// + /// A number that indicates the sign of , as shown in the following table. + /// + /// + /// + /// Return value + /// Meaning + /// + /// + /// + /// -1 + /// is less than zero. + /// + /// + /// 0 + /// is equal to zero. + /// + /// + /// 1 + /// is greater than zero. + /// + /// + /// + [Pure] + [MethodImpl(CompilerResources.MaxOptimization)] + public static int Sign(this TNumber value) + where TNumber : INumber + { + return TNumber.Sign(value); + } +} +#endif diff --git a/X10D/src/Math/SByteExtensions.cs b/X10D/src/Math/SByteExtensions.cs index a4c18f5..00cfa04 100644 --- a/X10D/src/Math/SByteExtensions.cs +++ b/X10D/src/Math/SByteExtensions.cs @@ -10,6 +10,7 @@ namespace X10D.Math; [CLSCompliant(false)] public static class SByteExtensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 8-bit signed integer. /// @@ -41,6 +42,7 @@ public static class SByteExtensions int root = System.Math.Abs(value).Mod(9); return (sbyte)(root < 1 ? 9 - root : root); } +#endif /// /// Returns the factorial of the current 8-bit signed integer. @@ -141,6 +143,7 @@ public static class SByteExtensions return (sbyte)((long)value).LowestCommonMultiple(other); } +#if !NET7_0_OR_GREATER /// /// Performs a modulo operation which supports a negative dividend. /// @@ -162,6 +165,7 @@ public static class SByteExtensions int r = dividend % divisor; return (sbyte)(r < 0 ? r + divisor : r); } +#endif /// /// Returns the multiplicative persistence of a specified value. diff --git a/X10D/src/Math/UInt16Extensions.cs b/X10D/src/Math/UInt16Extensions.cs index bffe978..8479cb2 100644 --- a/X10D/src/Math/UInt16Extensions.cs +++ b/X10D/src/Math/UInt16Extensions.cs @@ -10,6 +10,7 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt16Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 16-bit signed integer. /// @@ -41,6 +42,7 @@ public static class UInt16Extensions var root = (ushort)(value % 9); return (ushort)(root == 0 ? 9 : root); } +#endif /// /// Returns the factorial of the current 16-bit unsigned integer. diff --git a/X10D/src/Math/UInt32Extensions.cs b/X10D/src/Math/UInt32Extensions.cs index 3177425..6684e4f 100644 --- a/X10D/src/Math/UInt32Extensions.cs +++ b/X10D/src/Math/UInt32Extensions.cs @@ -10,6 +10,7 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt32Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 32-bit unsigned integer. /// @@ -41,6 +42,7 @@ public static class UInt32Extensions uint root = value % 9; return root == 0 ? 9 : root; } +#endif /// /// Returns the factorial of the current 32-bit unsigned integer. diff --git a/X10D/src/Math/UInt64Extensions.cs b/X10D/src/Math/UInt64Extensions.cs index 029deea..dc1e6f1 100644 --- a/X10D/src/Math/UInt64Extensions.cs +++ b/X10D/src/Math/UInt64Extensions.cs @@ -10,6 +10,7 @@ namespace X10D.Math; [CLSCompliant(false)] public static class UInt64Extensions { +#if !NET7_0_OR_GREATER /// /// Returns the number of digits in the current 64-bit unsigned integer. /// @@ -41,6 +42,7 @@ public static class UInt64Extensions ulong root = value % 9; return root == 0 ? 9 : root; } +#endif /// /// Returns the factorial of the current 64-bit unsigned integer. diff --git a/X10D/src/Time/Int16Extensions.cs b/X10D/src/Time/Int16Extensions.cs index fc0cfdb..0490c47 100644 --- a/X10D/src/Time/Int16Extensions.cs +++ b/X10D/src/Time/Int16Extensions.cs @@ -32,7 +32,7 @@ public static class Int16Extensions value++; } - return value.Mod(4) == 0 && (value.Mod(100) != 0 || value.Mod(400) == 0); + return value.Mod((short)4) == 0 && (value.Mod((short)100) != 0 || value.Mod((short)400) == 0); } /// diff --git a/X10D/src/Time/SByteExtensions.cs b/X10D/src/Time/SByteExtensions.cs index babd8d3..fea7719 100644 --- a/X10D/src/Time/SByteExtensions.cs +++ b/X10D/src/Time/SByteExtensions.cs @@ -33,7 +33,7 @@ public static class SByteExtensions value++; } - return value.Mod(4) == 0 && value.Mod(100) != 0; // mod 400 not required, sbyte.MaxValue is 127 anyway + return value.Mod((sbyte)4) == 0 && value.Mod((sbyte)100) != 0; // mod 400 not required, sbyte.MaxValue is 127 anyway } ///