1
0
mirror of https://github.com/oliverbooth/X10D synced 2024-11-22 17:38:47 +00:00

Compare commits

...

24 Commits

Author SHA1 Message Date
Oliver Booth
8ee2499961
Merge 19c467d88b into 8eaa01b505 2024-06-12 12:09:09 +01:00
8eaa01b505
test: TryWriteBytes should return false for smol span 2024-06-12 11:59:47 +01:00
68197ef5c7
fix: fix order of bytes in decimal write bytes methods 2024-06-12 11:57:27 +01:00
e5e27c0afd
[ci skip] test: remove NOSONAR markers 2024-06-12 11:52:31 +01:00
c338f4263e
chore: favour use of c# 12 2024-06-12 11:50:48 +01:00
19c467d88b
refactor!: move IsEven/IsOdd to NumberExtensions for .net>=7.0 2024-02-17 18:23:55 +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
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
40 changed files with 676 additions and 215 deletions

View File

@ -58,6 +58,7 @@ TypeInitializationException.
### Fixed ### Fixed
- X10D: Fixed `decimal.TryWriteBigEndianBytes` and `decimal.TryWriteLittleEndianBytes`.
- X10D.Hosting: Fixed `AddHostedSingleton` not accepting an interface as the service type. - X10D.Hosting: Fixed `AddHostedSingleton` not accepting an interface as the service type.
## [3.3.0] - 2023-08-21 ## [3.3.0] - 2023-08-21

View File

@ -5,7 +5,7 @@ or submit a pull request.
### Pull request guidelines ### Pull request guidelines
This project uses C# 11.0 language features where feasible, and adheres to StyleCop rules with some minor adjustments. This project uses C# 12.0 language features where feasible, and adheres to StyleCop rules with some minor adjustments.
There is an `.editorconfig` included in this repository. For quick and painless pull requests, ensure that the analyzer does not There is an `.editorconfig` included in this repository. For quick and painless pull requests, ensure that the analyzer does not
throw warnings. throw warnings.
@ -17,7 +17,7 @@ convetional commits specification.
Below are a few pointers to which you may refer, but keep in mind this is not an exhaustive list: Below are a few pointers to which you may refer, but keep in mind this is not an exhaustive list:
- Use C# 11.0 features where possible - Use C# 12.0 features where possible
- Try to ensure code is CLS-compliant. Where this is not possible, decorate methods with `CLSCompliantAttribute` and pass `false` - Try to ensure code is CLS-compliant. Where this is not possible, decorate methods with `CLSCompliantAttribute` and pass `false`
- Follow all .NET guidelines and coding conventions. - Follow all .NET guidelines and coding conventions.
See https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions See https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions

View File

@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<LangVersion>11.0</LangVersion> <LangVersion>12.0</LangVersion>
<Optimize>true</Optimize> <Optimize>true</Optimize>
<ImplicitUsings>true</ImplicitUsings> <ImplicitUsings>true</ImplicitUsings>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>

View File

@ -0,0 +1,72 @@
using NUnit.Framework;
using X10D.IO;
namespace X10D.Tests.IO;
[TestFixture]
internal class DecimalTests
{
[Test]
public void GetBigEndianBytes_ShouldReturnBytes_InBigEndian()
{
const decimal value = 1234m;
byte[] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 210];
byte[] bytes = value.GetBigEndianBytes();
CollectionAssert.AreEqual(expected, bytes);
}
[Test]
public void GetLittleEndianBytes_ShouldReturnBytes_InLittleEndian()
{
const decimal value = 1234m;
byte[] expected = [210, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
byte[] bytes = value.GetLittleEndianBytes();
CollectionAssert.AreEqual(expected, bytes);
}
[Test]
public void TryWriteBigEndianBytes_ShouldWriteBytes_InBigEndian()
{
const decimal value = 1234m;
byte[] expected = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 210];
Span<byte> bytes = stackalloc byte[16];
Assert.That(value.TryWriteBigEndianBytes(bytes));
CollectionAssert.AreEqual(expected, bytes.ToArray());
}
[Test]
public void TryWriteLittleEndianBytes_ShouldWriteBytes_InLittleEndian()
{
const decimal value = 1234m;
byte[] expected = [210, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
Span<byte> bytes = stackalloc byte[16];
Assert.That(value.TryWriteLittleEndianBytes(bytes));
CollectionAssert.AreEqual(expected, bytes.ToArray());
}
[Test]
public void TryWriteBigEndianBytes_ShouldReturnFalse_GivenSmallSpan()
{
const decimal value = 1234m;
Span<byte> bytes = Span<byte>.Empty;
Assert.That(value.TryWriteBigEndianBytes(bytes), Is.False);
}
[Test]
public void TryWriteLittleEndianBytes_ShouldReturnFalse_GivenSmallSpan()
{
const decimal value = 1234m;
Span<byte> bytes = Span<byte>.Empty;
Assert.That(value.TryWriteLittleEndianBytes(bytes), Is.False);
}
}

View File

@ -48,9 +48,10 @@ internal partial class StreamTests
Span<byte> actual = stackalloc byte[16]; Span<byte> actual = stackalloc byte[16];
ReadOnlySpan<byte> expected = stackalloc byte[] ReadOnlySpan<byte> expected = stackalloc byte[]
{ {
0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x10, 0x00, 0x00 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x68
}; };
int read = stream.Read(actual); int read = stream.Read(actual);
Trace.WriteLine(string.Join(' ', actual.ToArray()));
Assert.That(read, Is.EqualTo(16)); Assert.That(read, Is.EqualTo(16));
CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray()); CollectionAssert.AreEqual(expected.ToArray(), actual.ToArray());

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
using NUnit.Framework; using NUnit.Framework;
using X10D.Math; using X10D.Math;
namespace X10D.Tests.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.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
#endif
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
@ -14,6 +16,7 @@ public static class ByteExtensions
{ {
private const int Size = sizeof(byte) * 8; private const int Size = sizeof(byte) * 8;
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field. /// Unpacks this 8-bit unsigned integer into a boolean list, treating it as a bit field.
/// </summary> /// </summary>
@ -51,6 +54,7 @@ public static class ByteExtensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
#endif
[MethodImpl(CompilerResources.MaxOptimization)] [MethodImpl(CompilerResources.MaxOptimization)]
internal static void UnpackInternal_Fallback(this byte value, Span<bool> destination) 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.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
#endif
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
@ -14,6 +16,7 @@ public static class Int16Extensions
{ {
private const int Size = sizeof(short) * 8; private const int Size = sizeof(short) * 8;
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field. /// Unpacks this 16-bit signed integer into a boolean list, treating it as a bit field.
/// </summary> /// </summary>
@ -51,6 +54,7 @@ public static class Int16Extensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
#endif
[MethodImpl(CompilerResources.MaxOptimization)] [MethodImpl(CompilerResources.MaxOptimization)]
internal static void UnpackInternal_Fallback(this short value, Span<bool> destination) 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.CodeAnalysis;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
#endif
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics; using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
@ -14,6 +16,7 @@ public static class Int32Extensions
{ {
private const int Size = sizeof(int) * 8; private const int Size = sizeof(int) * 8;
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field. /// Unpacks this 32-bit signed integer into a boolean list, treating it as a bit field.
/// </summary> /// </summary>
@ -57,6 +60,7 @@ public static class Int32Extensions
UnpackInternal_Fallback(value, destination); UnpackInternal_Fallback(value, destination);
} }
#endif
[MethodImpl(CompilerResources.MaxOptimization)] [MethodImpl(CompilerResources.MaxOptimization)]
internal static void UnpackInternal_Fallback(this int value, Span<bool> destination) 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; using System.Diagnostics.Contracts;
namespace X10D.Collections; namespace X10D.Collections;
@ -41,3 +42,4 @@ public static class Int64Extensions
} }
} }
} }
#endif

View File

@ -92,14 +92,12 @@ public static class SpanExtensions
} }
// dotcover disable // dotcover disable
//NOSONAR
default: default:
#if NET7_0_OR_GREATER #if NET7_0_OR_GREATER
throw new UnreachableException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf<T>())); throw new UnreachableException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf<T>()));
#else #else
throw new ArgumentException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf<T>())); throw new ArgumentException(string.Format(ExceptionMessages.EnumSizeIsUnexpected, Unsafe.SizeOf<T>()));
#endif #endif
//NOSONAR
// dotcover enable // dotcover enable
} }
} }

View File

@ -1,4 +1,3 @@
using System.Diagnostics;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -13,11 +12,11 @@ public static class DecimalExtensions
/// Converts the current decimal number into an array of bytes, as little endian. /// Converts the current decimal number into an array of bytes, as little endian.
/// </summary> /// </summary>
/// <param name="value">The <see cref="int" /> value.</param> /// <param name="value">The <see cref="int" /> value.</param>
/// <returns>An array of bytes with length 4.</returns> /// <returns>An array of bytes with length 16.</returns>
[Pure] [Pure]
public static byte[] GetBigEndianBytes(this decimal value) public static byte[] GetBigEndianBytes(this decimal value)
{ {
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[16];
value.TryWriteBigEndianBytes(buffer); value.TryWriteBigEndianBytes(buffer);
return buffer.ToArray(); return buffer.ToArray();
} }
@ -26,11 +25,11 @@ public static class DecimalExtensions
/// Converts the current decimal number into an array of bytes, as little endian. /// Converts the current decimal number into an array of bytes, as little endian.
/// </summary> /// </summary>
/// <param name="value">The <see cref="int" /> value.</param> /// <param name="value">The <see cref="int" /> value.</param>
/// <returns>An array of bytes with length 4.</returns> /// <returns>An array of bytes with length 16.</returns>
[Pure] [Pure]
public static byte[] GetLittleEndianBytes(this decimal value) public static byte[] GetLittleEndianBytes(this decimal value)
{ {
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[16];
value.TryWriteLittleEndianBytes(buffer); value.TryWriteLittleEndianBytes(buffer);
return buffer.ToArray(); return buffer.ToArray();
} }
@ -44,23 +43,17 @@ public static class DecimalExtensions
public static bool TryWriteBigEndianBytes(this decimal value, Span<byte> destination) public static bool TryWriteBigEndianBytes(this decimal value, Span<byte> destination)
{ {
Span<int> buffer = stackalloc int[4]; Span<int> buffer = stackalloc int[4];
GetBits(value, buffer); decimal.GetBits(value, buffer);
Span<byte> result = stackalloc byte[16];
MemoryMarshal.Cast<int, byte>(buffer).CopyTo(result);
if (buffer[0].TryWriteBigEndianBytes(destination[..4]) &&
buffer[1].TryWriteBigEndianBytes(destination[4..8]) &&
buffer[2].TryWriteBigEndianBytes(destination[8..12]) &&
buffer[3].TryWriteBigEndianBytes(destination[12..]))
{
if (BitConverter.IsLittleEndian) if (BitConverter.IsLittleEndian)
{ {
destination.Reverse(); result.Reverse();
} }
return true; return result.TryCopyTo(destination);
}
destination.Clear();
return false;
} }
/// <summary> /// <summary>
@ -72,44 +65,16 @@ public static class DecimalExtensions
public static bool TryWriteLittleEndianBytes(this decimal value, Span<byte> destination) public static bool TryWriteLittleEndianBytes(this decimal value, Span<byte> destination)
{ {
Span<int> buffer = stackalloc int[4]; Span<int> buffer = stackalloc int[4];
GetBits(value, buffer); decimal.GetBits(value, buffer);
Span<byte> result = stackalloc byte[16];
MemoryMarshal.Cast<int, byte>(buffer).CopyTo(result);
if (buffer[0].TryWriteLittleEndianBytes(destination[..4]) &&
buffer[1].TryWriteLittleEndianBytes(destination[4..8]) &&
buffer[2].TryWriteLittleEndianBytes(destination[8..12]) &&
buffer[3].TryWriteLittleEndianBytes(destination[12..]))
{
if (!BitConverter.IsLittleEndian) if (!BitConverter.IsLittleEndian)
{ {
destination.Reverse(); result.Reverse();
} }
return true; return result.TryCopyTo(destination);
} }
destination.Clear();
return false;
}
private static void GetBits(decimal value, Span<int> destination)
{
_ = decimal.GetBits(value, destination);
}
#if !NET5_0_OR_GREATER
private static void WriteBits(Span<int> destination, Span<byte> buffer)
{
var flags = MemoryMarshal.Read<int>(buffer[..4]);
var hi = MemoryMarshal.Read<int>(buffer[4..8]);
var lo = MemoryMarshal.Read<long>(buffer[8..]);
var low = (uint)lo;
var mid = (uint)(lo >> 32);
destination[0] = (int)low;
destination[1] = (int)mid;
destination[2] = hi;
destination[3] = flags;
}
#endif
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
/// </summary> /// </summary>
public static class BigIntegerExtensions public static class BigIntegerExtensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current integer. /// Returns the number of digits in the current integer.
/// </summary> /// </summary>
@ -42,6 +43,7 @@ public static class BigIntegerExtensions
BigInteger root = BigInteger.Abs(value).Mod(9); BigInteger root = BigInteger.Abs(value).Mod(9);
return (int)(root == 0 ? 9 : root); return (int)(root == 0 ? 9 : root);
} }
#endif
/// <summary> /// <summary>
/// Returns the factorial of the current 64-bit signed integer. /// Returns the factorial of the current 64-bit signed integer.
@ -170,6 +172,7 @@ public static class BigIntegerExtensions
return value * other / value.GreatestCommonFactor(other); return value * other / value.GreatestCommonFactor(other);
} }
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Performs a modulo operation which supports a negative dividend. /// Performs a modulo operation which supports a negative dividend.
/// </summary> /// </summary>
@ -191,6 +194,7 @@ public static class BigIntegerExtensions
BigInteger r = dividend % divisor; BigInteger r = dividend % divisor;
return r < 0 ? r + divisor : r; return r < 0 ? r + divisor : r;
} }
#endif
/// <summary> /// <summary>
/// Returns the multiplicative persistence of a specified value. /// 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> /// </summary>
public static class ByteExtensions public static class ByteExtensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current 8-bit unsigned integer. /// Returns the number of digits in the current 8-bit unsigned integer.
/// </summary> /// </summary>
@ -107,6 +108,7 @@ public static class ByteExtensions
{ {
return !value.IsEven(); return !value.IsEven();
} }
#endif
/// <summary> /// <summary>
/// Returns a value indicating whether the current value is a prime number. /// Returns a value indicating whether the current value is a prime number.

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using X10D.CompilerServices; using X10D.CompilerServices;
@ -12,34 +13,6 @@ public static class MathUtility
private const double DefaultGamma = 2.2; private const double DefaultGamma = 2.2;
private const float DefaultGammaF = 2.2f; 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> /// <summary>
/// Calculates exponential decay for a value. /// Calculates exponential decay for a value.
/// </summary> /// </summary>
@ -154,6 +127,35 @@ public static class MathUtility
return (alpha - start) / (end - start); 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> /// <summary>
/// Linearly interpolates from one value to a target using a specified alpha. /// Linearly interpolates from one value to a target using a specified alpha.
/// </summary> /// </summary>
@ -190,6 +192,93 @@ public static class MathUtility
return ((1.0 - alpha) * value) + (alpha * target); 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> /// <summary>
/// Converts a linear value to a gamma-encoded value using a gamma value of <c>2.2</c>. /// Converts a linear value to a gamma-encoded value using a gamma value of <c>2.2</c>.
/// </summary> /// </summary>
@ -272,64 +361,6 @@ public static class MathUtility
return Unsafe.As<bool, int>(ref result); 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> /// <summary>
/// Calculates the sigmoid function for the given input value. /// Calculates the sigmoid function for the given input value.
/// </summary> /// </summary>
@ -358,18 +389,71 @@ public static class MathUtility
return 1.0f / (1.0f + System.Math.Exp(-value)); return 1.0f / (1.0f + System.Math.Exp(-value));
} }
#if NET7_0_OR_GREATER
/// <summary> /// <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> /// </summary>
/// <param name="value">The interpolation source.</param> /// <param name="value">The interpolation source.</param>
/// <param name="target">The interpolation target.</param> /// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param> /// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns> /// <returns>
public static float SmoothStep(float value, float target, float alpha) /// 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); // rookie mistake: a + t * (b - a)
alpha = -2.0f * alpha * alpha * alpha + 3.0f * alpha * alpha; // "precise" method: (1 - t) * a + t * b
return target * alpha + value * (1.0f - alpha); 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> /// <summary>
@ -379,10 +463,16 @@ public static class MathUtility
/// <param name="target">The interpolation target.</param> /// <param name="target">The interpolation target.</param>
/// <param name="alpha">The interpolation alpha.</param> /// <param name="alpha">The interpolation alpha.</param>
/// <returns>The interpolation result.</returns> /// <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); TNumber one = TNumber.One;
alpha = -2.0 * alpha * alpha * alpha + 3.0 * alpha * alpha; TNumber two = one + one;
return target * alpha + value * (1.0 - alpha); 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)] [CLSCompliant(false)]
public static class SByteExtensions public static class SByteExtensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current 8-bit signed integer. /// Returns the number of digits in the current 8-bit signed integer.
/// </summary> /// </summary>
@ -113,6 +114,7 @@ public static class SByteExtensions
{ {
return !value.IsEven(); return !value.IsEven();
} }
#endif
/// <summary> /// <summary>
/// Returns a value indicating whether the current value is a prime number. /// Returns a value indicating whether the current value is a prime number.
@ -141,6 +143,7 @@ public static class SByteExtensions
return (sbyte)((long)value).LowestCommonMultiple(other); return (sbyte)((long)value).LowestCommonMultiple(other);
} }
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Performs a modulo operation which supports a negative dividend. /// Performs a modulo operation which supports a negative dividend.
/// </summary> /// </summary>
@ -162,6 +165,7 @@ public static class SByteExtensions
int r = dividend % divisor; int r = dividend % divisor;
return (sbyte)(r < 0 ? r + divisor : r); return (sbyte)(r < 0 ? r + divisor : r);
} }
#endif
/// <summary> /// <summary>
/// Returns the multiplicative persistence of a specified value. /// Returns the multiplicative persistence of a specified value.

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)] [CLSCompliant(false)]
public static class UInt16Extensions public static class UInt16Extensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current 16-bit signed integer. /// Returns the number of digits in the current 16-bit signed integer.
/// </summary> /// </summary>
@ -94,20 +95,6 @@ public static class UInt16Extensions
return (value & 1) == 0; return (value & 1) == 0;
} }
/// <summary>
/// Returns a value indicating whether the current value is a prime number.
/// </summary>
/// <param name="value">The value whose primality to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is prime; otherwise, <see langword="false" />.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsPrime(this ushort value)
{
return ((ulong)value).IsPrime();
}
/// <summary> /// <summary>
/// Returns a value indicating whether the current value is not evenly divisible by 2. /// Returns a value indicating whether the current value is not evenly divisible by 2.
/// </summary> /// </summary>
@ -122,6 +109,21 @@ public static class UInt16Extensions
{ {
return !value.IsEven(); return !value.IsEven();
} }
#endif
/// <summary>
/// Returns a value indicating whether the current value is a prime number.
/// </summary>
/// <param name="value">The value whose primality to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is prime; otherwise, <see langword="false" />.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsPrime(this ushort value)
{
return ((ulong)value).IsPrime();
}
/// <summary> /// <summary>
/// Calculates the lowest common multiple between the current 16-bit unsigned integer, and another 16-bit unsigned /// Calculates the lowest common multiple between the current 16-bit unsigned integer, and another 16-bit unsigned

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)] [CLSCompliant(false)]
public static class UInt32Extensions public static class UInt32Extensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current 32-bit unsigned integer. /// Returns the number of digits in the current 32-bit unsigned integer.
/// </summary> /// </summary>
@ -94,20 +95,6 @@ public static class UInt32Extensions
return (value & 1) == 0; return (value & 1) == 0;
} }
/// <summary>
/// Returns a value indicating whether the current value is a prime number.
/// </summary>
/// <param name="value">The value whose primality to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is prime; otherwise, <see langword="false" />.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsPrime(this uint value)
{
return ((ulong)value).IsPrime();
}
/// <summary> /// <summary>
/// Returns a value indicating whether the current value is not evenly divisible by 2. /// Returns a value indicating whether the current value is not evenly divisible by 2.
/// </summary> /// </summary>
@ -122,6 +109,21 @@ public static class UInt32Extensions
{ {
return !value.IsEven(); return !value.IsEven();
} }
#endif
/// <summary>
/// Returns a value indicating whether the current value is a prime number.
/// </summary>
/// <param name="value">The value whose primality to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="value" /> is prime; otherwise, <see langword="false" />.
/// </returns>
[Pure]
[MethodImpl(CompilerResources.MaxOptimization)]
public static bool IsPrime(this uint value)
{
return ((ulong)value).IsPrime();
}
/// <summary> /// <summary>
/// Calculates the lowest common multiple between the current 32-bit unsigned integer, and another 32-bit unsigned /// Calculates the lowest common multiple between the current 32-bit unsigned integer, and another 32-bit unsigned

View File

@ -10,6 +10,7 @@ namespace X10D.Math;
[CLSCompliant(false)] [CLSCompliant(false)]
public static class UInt64Extensions public static class UInt64Extensions
{ {
#if !NET7_0_OR_GREATER
/// <summary> /// <summary>
/// Returns the number of digits in the current 64-bit unsigned integer. /// Returns the number of digits in the current 64-bit unsigned integer.
/// </summary> /// </summary>
@ -99,6 +100,22 @@ public static class UInt64Extensions
return (value & 1) == 0; return (value & 1) == 0;
} }
/// <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(this ulong value)
{
return !value.IsEven();
}
#endif
/// <summary> /// <summary>
/// Returns a value indicating whether the current value is a prime number. /// Returns a value indicating whether the current value is a prime number.
/// </summary> /// </summary>
@ -132,21 +149,6 @@ public static class UInt64Extensions
return true; return true;
} }
/// <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(this ulong value)
{
return !value.IsEven();
}
/// <summary> /// <summary>
/// Calculates the lowest common multiple between the current 64-bit unsigned integer, and another 64-bit unsigned /// Calculates the lowest common multiple between the current 64-bit unsigned integer, and another 64-bit unsigned
/// integer. /// integer.

View File

@ -98,7 +98,6 @@ public static class RuneExtensions
} }
// dotcover disable // dotcover disable
//NOSONAR
default: default:
string exceptionFormat = ExceptionMessages.UnexpectedRuneUtf8SequenceLength; string exceptionFormat = ExceptionMessages.UnexpectedRuneUtf8SequenceLength;
string message = string.Format(CultureInfo.CurrentCulture, exceptionFormat, length); string message = string.Format(CultureInfo.CurrentCulture, exceptionFormat, length);
@ -107,7 +106,6 @@ public static class RuneExtensions
#else #else
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
#endif #endif
//NOSONAR
// dotcover enable // dotcover enable
} }
} }

View File

@ -32,7 +32,7 @@ public static class Int16Extensions
value++; 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> /// <summary>

View File

@ -33,7 +33,7 @@ public static class SByteExtensions
value++; 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> /// <summary>