1
0
mirror of https://github.com/oliverbooth/X10D synced 2024-11-09 22:55:42 +00:00

Compare commits

...

20 Commits

Author SHA1 Message Date
Oliver Booth
6cce6d83bd
Merge 3e0853d102 into bd1b12da71 2024-02-17 18:17:22 +00:00
3e0853d102
refactor!: move GCF to BinaryIntegerExtensions for .net>=7 2024-02-17 18:17:12 +00:00
30f158e861
refactor!: move Factorial to BinaryIntegerExtensions for .net>=7 2024-02-17 18:13:45 +00:00
bd1b12da71
docs: update 3.x.x migration docs 2024-02-17 17:54:06 +00:00
eb46257b75
Merge branch 'release/4.0.0' into feature/generic_math 2024-02-17 17:27:56 +00:00
69f0fa7b80
Merge branch 'release/4.0.0' into feature/generic_math 2024-02-17 16:12:39 +00:00
8cdbfcfe0d
Merge branch 'release/4.0.0' into feature/generic_math 2024-02-17 15:31:02 +00:00
99e0bef64d
Merge branch 'release/4.0.0' into feature/generic_math 2024-02-12 09:38:46 +00:00
cc94905930
Merge branch 'release/4.0.0' into feature/generic_math 2024-01-06 17:12:01 +00:00
458ab03be0
style: remove double blank line 2024-01-06 17:03:19 +00:00
be44b9d549
Merge branch 'release/4.0.0' into feature/generic_math 2024-01-06 17:01:42 +00:00
bb2f67e9b6
style: remove UTF8 BOM 2023-08-26 18:08:14 +01:00
2da8c7db7a
refactor: rename TNumber as TInteger 2023-08-25 02:53:06 +01:00
b8f85e4270
refactor: CountDigits is Pure. also honour methodimpl 2023-08-25 02:50:15 +01:00
39dfea7622
[ci skip] fix: define CountDigits in the correct file
I'm an idiot.
2023-08-25 02:42:51 +01:00
423fb90cc4
feat: add IBinaryInteger<T>.CountDigits 2023-08-25 02:34:55 +01:00
8bc7372ba4
Merge branch 'release/4.0.0' into feature/generic_math 2023-08-25 02:27:08 +01:00
21271314af
feat: add generic math where possible in MathUtility 2023-04-05 17:36:49 +01:00
b91aad6305
feat: convert DigitalRoot and Mod to generic math 2023-04-05 15:35:25 +01:00
87a85b82d9
feat: add Unpack for IBinaryInteger<T>
This introduces an experiment, to add support for generic math in X10D. So far, build and tests remain passing - this is a good sign. There is potential here.

This API is subject to change and may be removed without warning.
2023-04-05 15:11:43 +01:00
33 changed files with 554 additions and 111 deletions

View File

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;

View File

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;

View File

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;

View File

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using X10D.Math;
namespace X10D.Tests.Math;

View File

@ -1,4 +1,4 @@
using NUnit.Framework;
using NUnit.Framework;
using X10D.Math;
namespace X10D.Tests.Math;

View File

@ -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;
/// <summary>
/// Collection-related extension methods for <see cref="IBinaryInteger{T}" />.
/// </summary>
public static class BinaryIntegerExtensions
{
/// <summary>
/// Unpacks this integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <returns>An array of <see cref="bool" /> with a length equal to the size of <typeparamref name="TInteger" />.</returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool[] Unpack<TInteger>(this TInteger value)
where TInteger : unmanaged, IBinaryInteger<TInteger>
{
unsafe
{
var buffer = new bool[sizeof(TInteger) * 8];
value.Unpack(buffer);
return buffer;
}
}
/// <summary>
/// Unpacks this integer into a boolean list, treating it as a bit field.
/// </summary>
/// <param name="value">The value to unpack.</param>
/// <param name="destination">When this method returns, contains the unpacked booleans from <paramref name="value" />.</param>
/// <exception cref="ArgumentException"><paramref name="destination" /> is not large enough to contain the result.</exception>
[MethodImpl(CompilerResources.MaxOptimization)]
public static void Unpack<TInteger>(this TInteger value, Span<bool> destination)
where TInteger : unmanaged, IBinaryInteger<TInteger>
{
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<TInteger>(this TInteger value, Span<bool> destination)
where TInteger : unmanaged, IBinaryInteger<TInteger>
{
unsafe
{
int bitCount = sizeof(TInteger) * 8;
for (var index = 0; index < bitCount; index++)
{
destination[index] = (value & (TInteger.One << index)) != TInteger.Zero;
}
}
}
}
#endif

View File

@ -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
/// <summary>
/// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field.
/// </summary>
@ -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<bool> destination)

View File

@ -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
/// <summary>
/// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
@ -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<bool> destination)

View File

@ -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
/// <summary>
/// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field.
/// </summary>
@ -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<bool> destination)

View File

@ -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

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -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;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -1,4 +1,4 @@
using System.Buffers.Binary;
using System.Buffers.Binary;
using System.Diagnostics.Contracts;
namespace X10D.IO;

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
/// </summary>
public static class BigIntegerExtensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current integer.
/// </summary>
@ -42,6 +43,7 @@ public static class BigIntegerExtensions
BigInteger root = BigInteger.Abs(value).Mod(9);
return (int)(root == 0 ? 9 : root);
}
#endif
/// <summary>
/// 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
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
@ -191,6 +194,7 @@ public static class BigIntegerExtensions
BigInteger r = dividend % divisor;
return r < 0 ? r + divisor : r;
}
#endif
/// <summary>
/// Returns the multiplicative persistence of a specified value.

View File

@ -0,0 +1,101 @@
#if NET7_0_OR_GREATER
using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices;
using X10D.CompilerServices;
namespace X10D.Math;
/// <summary>
/// Math-related extension methods for <see cref="IBinaryInteger{TSelf}" />.
/// </summary>
public static class BinaryIntegerExtensions
{
/// <summary>
/// Returns the number of digits in the current binary integer.
/// </summary>
/// <param name="value">The value whose digit count to compute.</param>
/// <returns>The number of digits in <paramref name="value" />.</returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static int CountDigits<TInteger>(this TInteger value)
where TInteger : IBinaryInteger<TInteger>
{
if (TInteger.IsZero(value))
{
return 1;
}
return 1 + (int)System.Math.Floor(System.Math.Log10(System.Math.Abs(double.CreateChecked(value))));
}
/// <summary>
/// Computes the digital root of this integer.
/// </summary>
/// <param name="value">The value whose digital root to compute.</param>
/// <returns>The digital root of <paramref name="value" />.</returns>
/// <remarks>The digital root is defined as the recursive sum of digits until that result is a single digit.</remarks>
/// <remarks>
/// <para>The digital root is defined as the recursive sum of digits until that result is a single digit.</para>
/// <para>For example, the digital root of 239 is 5: <c>2 + 3 + 9 = 14</c>, then <c>1 + 4 = 5</c>.</para>
/// </remarks>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static int DigitalRoot<TInteger>(this TInteger value)
where TInteger : IBinaryInteger<TInteger>
{
var nine = TInteger.CreateChecked(9);
TInteger root = TInteger.Abs(value).Mod(nine);
return int.CreateChecked(root == TInteger.Zero ? nine : root);
}
/// <summary>
/// Returns the factorial of the current binary integer.
/// </summary>
/// <param name="value">The value whose factorial to compute.</param>
/// <returns>The factorial of <paramref name="value" />.</returns>
/// <exception cref="ArithmeticException"><paramref name="value" /> is less than 0.</exception>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static long Factorial<TInteger>(this TInteger value)
where TInteger : IBinaryInteger<TInteger>
{
if (value < TInteger.Zero)
{
throw new ArithmeticException(nameof(value));
}
if (value == TInteger.Zero)
{
return 1;
}
var result = 1L;
for (TInteger i = TInteger.One; i <= value; i++)
{
result *= long.CreateChecked(i);
}
return result;
}
/// <summary>
/// Calculates the greatest common factor between the current binary integer, and another binary integer.
/// </summary>
/// <param name="value">The first value.</param>
/// <param name="other">The second value.</param>
/// <returns>The greatest common factor between <paramref name="value" /> and <paramref name="other" />.</returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static TInteger GreatestCommonFactor<TInteger>(this TInteger value, TInteger other)
where TInteger : IBinaryInteger<TInteger>
{
while (other != TInteger.Zero)
{
(value, other) = (other, value % other);
}
return value;
}
}
#endif

View File

@ -9,6 +9,7 @@ namespace X10D.Math;
/// </summary>
public static class ByteExtensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 8-bit unsigned integer.
/// </summary>
@ -77,6 +78,7 @@ public static class ByteExtensions
{
return (byte)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.

View File

@ -9,6 +9,7 @@ namespace X10D.Math;
/// </summary>
public static class Int16Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 16-bit signed integer.
/// </summary>
@ -82,6 +83,7 @@ public static class Int16Extensions
{
return (short)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.
@ -140,6 +142,7 @@ public static class Int16Extensions
return (short)((long)value).LowestCommonMultiple(other);
}
#if !NET7_0_OR_GREATER
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
@ -161,6 +164,7 @@ public static class Int16Extensions
int r = dividend % divisor;
return (short)(r < 0 ? r + divisor : r);
}
#endif
/// <summary>
/// Returns the multiplicative persistence of a specified value.

View File

@ -9,6 +9,7 @@ namespace X10D.Math;
/// </summary>
public static class Int32Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 32-bit signed integer.
/// </summary>
@ -82,6 +83,7 @@ public static class Int32Extensions
{
return (int)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.
@ -140,6 +142,7 @@ public static class Int32Extensions
return (int)((long)value).LowestCommonMultiple(other);
}
#if !NET7_0_OR_GREATER
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
@ -161,6 +164,7 @@ public static class Int32Extensions
int r = dividend % divisor;
return r < 0 ? r + divisor : r;
}
#endif
/// <summary>
/// Returns the multiplicative persistence of a specified value.

View File

@ -9,6 +9,7 @@ namespace X10D.Math;
/// </summary>
public static class Int64Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 64-bit signed integer.
/// </summary>
@ -87,6 +88,7 @@ public static class Int64Extensions
return value;
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.
@ -179,6 +181,7 @@ public static class Int64Extensions
return value * other / value.GreatestCommonFactor(other);
}
#if !NET7_0_OR_GREATER
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
@ -200,6 +203,7 @@ public static class Int64Extensions
long r = dividend % divisor;
return r < 0 ? r + divisor : r;
}
#endif
/// <summary>
/// Returns the multiplicative persistence of a specified value.

View File

@ -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;
/// <summary>
/// Applies a simple bias function to value.
/// </summary>
/// <param name="value">The value to which the bias function will be applied.</param>
/// <param name="bias">The bias value. Valid values range from 0-1.</param>
/// <returns>The biased result.</returns>
/// <remarks>
/// If <paramref name="bias" /> is less than 0.5, <paramref name="value" /> will be shifted downward; otherwise, upward.
/// </remarks>
public static float Bias(float value, float bias)
{
return value / ((1.0f / bias - 2.0f) * (1.0f - value) + 1.0f);
}
/// <summary>
/// Applies a simple bias function to value.
/// </summary>
/// <param name="value">The value to which the bias function will be applied.</param>
/// <param name="bias">The bias value. Valid values range from 0-1.</param>
/// <returns>The biased result.</returns>
/// <remarks>
/// If <paramref name="bias" /> is less than 0.5, <paramref name="value" /> will be shifted downward; otherwise, upward.
/// </remarks>
public static double Bias(double value, double bias)
{
return value / ((1.0 / bias - 2.0) * (1.0 - value) + 1.0);
}
/// <summary>
/// Calculates exponential decay for a value.
/// </summary>
@ -154,6 +127,35 @@ public static class MathUtility
return (alpha - start) / (end - start);
}
#if !NET7_0_OR_GREATER
/// <summary>
/// Applies a simple bias function to value.
/// </summary>
/// <param name="value">The value to which the bias function will be applied.</param>
/// <param name="bias">The bias value. Valid values range from 0-1.</param>
/// <returns>The biased result.</returns>
/// <remarks>
/// If <paramref name="bias" /> is less than 0.5, <paramref name="value" /> will be shifted downward; otherwise, upward.
/// </remarks>
public static float Bias(float value, float bias)
{
return value / ((1.0f / bias - 2.0f) * (1.0f - value) + 1.0f);
}
/// <summary>
/// Applies a simple bias function to value.
/// </summary>
/// <param name="value">The value to which the bias function will be applied.</param>
/// <param name="bias">The bias value. Valid values range from 0-1.</param>
/// <returns>The biased result.</returns>
/// <remarks>
/// If <paramref name="bias" /> is less than 0.5, <paramref name="value" /> will be shifted downward; otherwise, upward.
/// </remarks>
public static double Bias(double value, double bias)
{
return value / ((1.0 / bias - 2.0) * (1.0 - value) + 1.0);
}
/// <summary>
/// Linearly interpolates from one value to a target using a specified alpha.
/// </summary>
@ -190,6 +192,93 @@ public static class MathUtility
return ((1.0 - alpha) * value) + (alpha * target);
}
/// <summary>
/// Performs smooth Hermite interpolation from one value to a target using a specified alpha.
/// </summary>
/// <param name="value">The interpolation source.</param>
/// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns>
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);
}
/// <summary>
/// Performs smooth Hermite interpolation from one value to a target using a specified alpha.
/// </summary>
/// <param name="value">The interpolation source.</param>
/// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns>
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);
}
/// <summary>
/// Converts a value from being a percentage of one range, to being the same percentage in a new range.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="oldMin">The old minimum value.</param>
/// <param name="oldMax">The old maximum value.</param>
/// <param name="newMin">The new minimum value.</param>
/// <param name="newMax">The new maximum value.</param>
/// <returns>The scaled value.</returns>
[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;
}
/// <summary>
/// Converts a value from being a percentage of one range, to being the same percentage in a new range.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="oldMin">The old minimum value.</param>
/// <param name="oldMax">The old maximum value.</param>
/// <param name="newMin">The new minimum value.</param>
/// <param name="newMax">The new maximum value.</param>
/// <returns>The scaled value.</returns>
[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;
}
/// <summary>
/// Returns the incremental sawtooth wave of a given value.
/// </summary>
/// <param name="value">The value to calculate.</param>
/// <returns>The sawtooth wave of the given value.</returns>
public static float Sawtooth(float value)
{
return (value - MathF.Floor(value));
}
/// <summary>
/// Returns the incremental sawtooth wave of a given value.
/// </summary>
/// <param name="value">The value to calculate.</param>
/// <returns>The sawtooth wave of the given value.</returns>
public static double Sawtooth(double value)
{
return (value - System.Math.Floor(value));
}
#endif
/// <summary>
/// Converts a linear value to a gamma-encoded value using a gamma value of <c>2.2</c>.
/// </summary>
@ -272,64 +361,6 @@ public static class MathUtility
return Unsafe.As<bool, int>(ref result);
}
/// <summary>
/// Returns the incremental sawtooth wave of a given value.
/// </summary>
/// <param name="value">The value to calculate.</param>
/// <returns>The sawtooth wave of the given value.</returns>
public static float Sawtooth(float value)
{
return (value - MathF.Floor(value));
}
/// <summary>
/// Returns the incremental sawtooth wave of a given value.
/// </summary>
/// <param name="value">The value to calculate.</param>
/// <returns>The sawtooth wave of the given value.</returns>
public static double Sawtooth(double value)
{
return (value - System.Math.Floor(value));
}
/// <summary>
/// Converts a value from being a percentage of one range, to being the same percentage in a new range.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="oldMin">The old minimum value.</param>
/// <param name="oldMax">The old maximum value.</param>
/// <param name="newMin">The new minimum value.</param>
/// <param name="newMax">The new maximum value.</param>
/// <returns>The scaled value.</returns>
[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;
}
/// <summary>
/// Converts a value from being a percentage of one range, to being the same percentage in a new range.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="oldMin">The old minimum value.</param>
/// <param name="oldMax">The old maximum value.</param>
/// <param name="newMin">The new minimum value.</param>
/// <param name="newMax">The new maximum value.</param>
/// <returns>The scaled value.</returns>
[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;
}
/// <summary>
/// Calculates the sigmoid function for the given input value.
/// </summary>
@ -358,18 +389,71 @@ public static class MathUtility
return 1.0f / (1.0f + System.Math.Exp(-value));
}
#if NET7_0_OR_GREATER
/// <summary>
/// Performs smooth Hermite interpolation from one value to a target using a specified alpha.
/// Applies a simple bias function to value.
/// </summary>
/// <param name="value">The value to which the bias function will be applied.</param>
/// <param name="bias">The bias value. Valid values range from 0-1.</param>
/// <returns>The biased result.</returns>
/// <remarks>
/// If <paramref name="bias" /> is less than 0.5, <paramref name="value" /> will be shifted downward; otherwise, upward.
/// </remarks>
public static TNumber Bias<TNumber>(TNumber value, TNumber bias)
where TNumber : INumber<TNumber>
{
TNumber identity = TNumber.MultiplicativeIdentity;
return value / ((identity / bias - TNumber.CreateChecked(2)) * (identity - value) + identity);
}
/// <summary>
/// Linearly interpolates from one value to a target using a specified alpha.
/// </summary>
/// <param name="value">The interpolation source.</param>
/// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns>
public static float SmoothStep(float value, float target, float alpha)
/// <returns>
/// The interpolation result as determined by <c>(1 - alpha) * value + alpha * target</c>.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static TNumber Lerp<TNumber>(TNumber value, TNumber target, TNumber alpha)
where TNumber : INumber<TNumber>
{
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);
}
/// <summary>
/// Returns the incremental sawtooth wave of a given value.
/// </summary>
/// <param name="value">The value to calculate.</param>
/// <returns>The sawtooth wave of the given value.</returns>
public static TNumber Sawtooth<TNumber>(TNumber value)
where TNumber : IFloatingPoint<TNumber>
{
return (value - TNumber.Floor(value));
}
/// <summary>
/// Converts a value from being a percentage of one range, to being the same percentage in a new range.
/// </summary>
/// <param name="value">The value to convert.</param>
/// <param name="oldMin">The old minimum value.</param>
/// <param name="oldMax">The old maximum value.</param>
/// <param name="newMin">The new minimum value.</param>
/// <param name="newMax">The new maximum value.</param>
/// <returns>The scaled value.</returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static TNumber ScaleRange<TNumber>(TNumber value, TNumber oldMin, TNumber oldMax, TNumber newMin, TNumber newMax)
where TNumber : INumber<TNumber>
{
TNumber oldRange = oldMax - oldMin;
TNumber newRange = newMax - newMin;
TNumber alpha = (value - oldMin) / oldRange;
return (alpha * newRange) + newMin;
}
/// <summary>
@ -379,10 +463,16 @@ public static class MathUtility
/// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns>
public static double SmoothStep(double value, double target, double alpha)
public static TNumber SmoothStep<TNumber>(TNumber value, TNumber target, TNumber alpha)
where TNumber : INumber<TNumber>
{
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
}

View File

@ -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;
/// <summary>
/// Math-related extension methods for <see cref="INumber{TSelf}" />.
/// </summary>
public static class NumberExtensions
{
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.
/// </summary>
/// <param name="value">The value whose parity to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is evenly divisible by 2, or <see langword="false" />
/// otherwise.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsEven<TNumber>(this TNumber value)
where TNumber : INumber<TNumber>
{
return value % TNumber.CreateChecked(2) == TNumber.Zero;
}
/// <summary>
/// Returns a value indicating whether the current value is not evenly divisible by 2.
/// </summary>
/// <param name="value">The value whose parity to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is not evenly divisible by 2, or <see langword="false" />
/// otherwise.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsOdd<TNumber>(this TNumber value)
where TNumber : INumber<TNumber>
{
return !value.IsEven();
}
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
/// <param name="dividend">The dividend.</param>
/// <param name="divisor">The divisor.</param>
/// <returns>The result of <c>dividend mod divisor</c>.</returns>
/// <remarks>
/// The <c>%</c> 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 <c>x % y</c> where x is
/// negative will return a negative value, akin to <c>-(x % y)</c>, even if precedence is forced. This method provides a
/// modulo operation which supports negative dividends.
/// </remarks>
/// <author>ShreevatsaR, https://stackoverflow.com/a/1082938/1467293</author>
/// <license>CC-BY-SA 2.5</license>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static TNumber Mod<TNumber>(this TNumber dividend, TNumber divisor)
where TNumber : INumber<TNumber>
{
TNumber r = dividend % divisor;
return r < TNumber.Zero ? r + divisor : r;
}
/// <summary>
/// Returns an integer that indicates the sign of this number.
/// </summary>
/// <param name="value">A signed number.</param>
/// <returns>
/// A number that indicates the sign of <paramref name="value" />, as shown in the following table.
///
/// <list type="table">
/// <listheader>
/// <term>Return value</term>
/// <description>Meaning</description>
/// </listheader>
///
/// <item>
/// <term>-1</term>
/// <description><paramref name="value" /> is less than zero.</description>
/// </item>
/// <item>
/// <term>0</term>
/// <description><paramref name="value" /> is equal to zero.</description>
/// </item>
/// <item>
/// <term>1</term>
/// <description><paramref name="value" /> is greater than zero.</description>
/// </item>
/// </list>
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static int Sign<TNumber>(this TNumber value)
where TNumber : INumber<TNumber>
{
return TNumber.Sign(value);
}
}
#endif

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)]
public static class SByteExtensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 8-bit signed integer.
/// </summary>
@ -83,6 +84,7 @@ public static class SByteExtensions
{
return (sbyte)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.
@ -141,6 +143,7 @@ public static class SByteExtensions
return (sbyte)((long)value).LowestCommonMultiple(other);
}
#if !NET7_0_OR_GREATER
/// <summary>
/// Performs a modulo operation which supports a negative dividend.
/// </summary>
@ -162,6 +165,7 @@ public static class SByteExtensions
int r = dividend % divisor;
return (sbyte)(r < 0 ? r + divisor : r);
}
#endif
/// <summary>
/// Returns the multiplicative persistence of a specified value.

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)]
public static class UInt16Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 16-bit signed integer.
/// </summary>
@ -78,6 +79,7 @@ public static class UInt16Extensions
{
return (ushort)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)]
public static class UInt32Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 32-bit unsigned integer.
/// </summary>
@ -78,6 +79,7 @@ public static class UInt32Extensions
{
return (uint)((long)value).GreatestCommonFactor(other);
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)]
public static class UInt64Extensions
{
#if !NET7_0_OR_GREATER
/// <summary>
/// Returns the number of digits in the current 64-bit unsigned integer.
/// </summary>
@ -83,6 +84,7 @@ public static class UInt64Extensions
return value;
}
#endif
/// <summary>
/// Returns a value indicating whether the current value is evenly divisible by 2.

View File

@ -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);
}
/// <summary>

View File

@ -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
}
/// <summary>

View File

@ -3,6 +3,9 @@
X10D 4.0.0 is a major release that introduces breaking changes. This document will help you migrate your code from 3.x.x
to 4.0.0.
When a breaking change is mentioned, the compatibility mirrors that of the Microsoft documentation for .NET, which you
can find [here](https://learn.microsoft.com/en-us/dotnet/core/compatibility/categories).
## Removed APIs
### X10D.DSharpPlus library
@ -12,8 +15,18 @@ wrapper library. However, I have since moved to using a different library, and a
scope of X10D or in my best interest to maintain it. The library will remain available on NuGet until DSharpPlus release
5.0.0 as stable, and X10D.DSharpPlus will NOT be part of X10D 4.0.0. I'm sorry for any inconvenience this may cause.
### X10D.Unity library
The `X10D.Unity` library has been removed. This library was used to provide extension methods for the Unity API. Due to
game development politics, I no longer feel it in my best interest to continue development of the Unity package. The
library will remain on NuGet for the foreseeable future but will no longer be maintained. The `upm` branch of the Git
repository will remain available indefinitely also.
### `Endianness` enum
**Source incompatible change**
The `Endianness` enum was used to specify the endianness of data when reading or writing to a stream. This was causing
some clutter, and makes it harder to develop X10D, so it was removed. In its stead, any method which accepted an
`Endianness` parameter now has two overloads: one for big-endian, and one for little-endian. For example, the following
@ -41,12 +54,16 @@ Span<byte> buffer = stackalloc byte[4];
### `IEnumerable<T>.ConcatOne(T)` extension method
**Source incompatible change**
The `IEnumerable<T>.ConcatOne` extension method was used to concatenate a single item to an enumerable. At the time, I
was unaware of the `Enumerable.Append` method, which does the same thing. As such, `ConcatOne` has been removed. There
is no migration path for this, as the built in `Append` method from LINQ is a drop-in replacement.
## Exception Changes
**Source incompatible change**
If you were previously catching TypeInitializationException when calling `Stream.GetHash<>` or `Stream.TryWriteHash<>`,
you will now need to catch a ArgumentException instead. The justification for this change is that ArgumentException is
more general, and more easily understood by developers.