diff --git a/src/MissingValues.Benchmarks/BigIntegerBenchmarks.cs b/src/MissingValues.Benchmarks/BigIntegerBenchmarks.cs index 94f3a94..a9bbdfd 100644 --- a/src/MissingValues.Benchmarks/BigIntegerBenchmarks.cs +++ b/src/MissingValues.Benchmarks/BigIntegerBenchmarks.cs @@ -3,136 +3,82 @@ namespace MissingValues.Benchmarks; +[MemoryDiagnoser] [MinColumn, MaxColumn, MeanColumn, MedianColumn] [ShortRunJob] public class BigIntegerBenchmarks { [ParamsSource(nameof(ValuesSource))] public UInt128 Value { get; set; } - BigInteger b256, b512; - UInt256 u256; - UInt512 u512; [GlobalSetup] public void Setup() { - u256 = (UInt256)(Int256.MaxValue / 2); - u512 = (UInt512)(Int512.MaxValue / 2); - - Span bytes = stackalloc byte[UInt256.Size]; - Int256.MaxValue.TryWriteLittleEndian(bytes); - - b256 = new BigInteger(bytes, true); - - bytes = stackalloc byte[UInt512.Size]; - Int512.MaxValue.TryWriteLittleEndian(bytes); - - b512 = new BigInteger(bytes, true); } [Benchmark] - public BigInteger Add_BigInteger256() - { - return b256 + Value; - } - [Benchmark] - public BigInteger Add_BigInteger512() - { - return b512 + Value; - } - [Benchmark] - public UInt256 Add_UInt256() + [ArgumentsSource(nameof(UInt256ParamsSource))] + public UInt256 Add_UInt256(UInt256 a) { - return u256 + Value; + return a + Value; } [Benchmark] - public UInt512 Add_UInt512() + [ArgumentsSource(nameof(UInt512ParamsSource))] + public UInt512 Add_UInt512(UInt512 a) { - return u512 + Value; + return a + Value; } [Benchmark] - public BigInteger Subtract_BigInteger256() + [ArgumentsSource(nameof(UInt256ParamsSource))] + public UInt256 Subtract_UInt256(UInt256 a) { - return b256 - Value; + return a - Value; } [Benchmark] - public BigInteger Subtract_BigInteger512() + [ArgumentsSource(nameof(UInt512ParamsSource))] + public UInt512 Subtract_UInt512(UInt512 a) { - return b512 - Value; - } - [Benchmark] - public UInt256 Subtract_UInt256() - { - return u256 - Value; - } - [Benchmark] - public UInt512 Subtract_UInt512() - { - return u512 - Value; + return a - Value; } [Benchmark] - public BigInteger Multiply_BigInteger256() - { - return b256 * Value; - } - [Benchmark] - public BigInteger Multiply_BigInteger512() + [ArgumentsSource(nameof(UInt256ParamsSource))] + public UInt256 Multiply_UInt256(UInt256 a) { - return b512 * Value; + return a * Value; } [Benchmark] - public UInt256 Multiply_UInt256() + [ArgumentsSource(nameof(UInt512ParamsSource))] + public UInt512 Multiply_UInt512(UInt512 a) { - return u256 * Value; - } - [Benchmark] - public UInt512 Multiply_UInt512() - { - return u512 * Value; + return a * Value; } [Benchmark] - public BigInteger Divide_BigInteger256() - { - return b256 / Value; - } - [Benchmark] - public BigInteger Divide_BigInteger512() + [ArgumentsSource(nameof(UInt256ParamsSource))] + public UInt256 Divide_UInt256(UInt256 a) { - return b512 / Value; + return a / Value; } [Benchmark] - public UInt256 Divide_UInt256() + [ArgumentsSource(nameof(UInt512ParamsSource))] + public UInt512 Divide_UInt512(UInt512 a) { - return u256 / Value; - } - [Benchmark] - public UInt512 Divide_UInt512() - { - return u512 / Value; + return a / Value; } [Benchmark] - public BigInteger Remainder_BigInteger256() + [ArgumentsSource(nameof(UInt256ParamsSource))] + public UInt256 Remainder_UInt256(UInt256 a) { - return b256 % Value; + return a % Value; } [Benchmark] - public BigInteger Remainder_BigInteger512() + [ArgumentsSource(nameof(UInt512ParamsSource))] + public UInt512 Remainder_UInt512(UInt512 a) { - return b512 % Value; - } - [Benchmark] - public UInt256 Remainder_UInt256() - { - return u256 % Value; - } - [Benchmark] - public UInt512 Remainder_UInt512() - { - return u512 % Value; + return a % Value; } public IEnumerable ValuesSource() @@ -142,4 +88,20 @@ public IEnumerable ValuesSource() yield return UInt128.Parse("10000000000000000000"); yield return UInt128.Parse("100000000000000000000000000000000000000"); } + public IEnumerable UInt256ParamsSource() + { + yield return UInt256.One; + yield return uint.MaxValue; + yield return ulong.MaxValue; + yield return UInt128.MaxValue; + yield return UInt256.MaxValue / UInt256.Parse("10000000000000000000000000000000000000000"); + } + public IEnumerable UInt512ParamsSource() + { + yield return UInt512.One; + yield return uint.MaxValue; + yield return ulong.MaxValue; + yield return UInt128.MaxValue; + yield return UInt512.MaxValue / UInt512.Parse("10000000000000000000000000000000000000000"); + } } \ No newline at end of file diff --git a/src/MissingValues.Benchmarks/CopyValueBenchmarks.cs b/src/MissingValues.Benchmarks/CopyValueBenchmarks.cs new file mode 100644 index 0000000..a6adf97 --- /dev/null +++ b/src/MissingValues.Benchmarks/CopyValueBenchmarks.cs @@ -0,0 +1,280 @@ +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis.Text; +using MissingValues; +using System.Drawing; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[MinColumn, MaxColumn, MeanColumn, MedianColumn] +[ShortRunJob] +public class WriteUnalignedBenchmarks +{ + private int _index = 1; + + [Benchmark] + [ArgumentsSource(nameof(UInt256ParamsSource))] + public unsafe uint UInt256_UnsafePointer_WriteByUInt32(UInt256 u) + { + const int UIntCount = 32 / sizeof(uint); + + uint* p = stackalloc uint[UIntCount]; + + Unsafe.WriteUnaligned(ref *(byte*)(p + 0), (uint)(u.Part0 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 1), (uint)(u.Part0 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 2), (uint)(u.Part1 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 3), (uint)(u.Part1 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 4), (uint)(u.Part2 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 5), (uint)(u.Part2 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 6), (uint)(u.Part3 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 7), (uint)(u.Part3 >> 32)); + + Span left = new Span(p, (UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32)); + + return GetItem(left); + } + [Benchmark] + [ArgumentsSource(nameof(UInt256ParamsSource))] + public unsafe uint UInt256_UnsafePointer_WriteByUInt256(UInt256 u) + { + const int UIntCount = 32 / sizeof(uint); + + uint* p = stackalloc uint[UIntCount]; + Unsafe.WriteUnaligned(ref *(byte*)(p), u); + Span left = new Span(p, (UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32)); + + return GetItem(left); + } + [Benchmark] + [ArgumentsSource(nameof(UInt256ParamsSource))] + public uint UInt256_Ref_WriteByUInt32(UInt256 u) + { + const int UIntCount = 32 / sizeof(uint); + Span p = stackalloc uint[UIntCount]; + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 0), (uint)(u.Part0 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 1), (uint)(u.Part0 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 2), (uint)(u.Part1 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 3), (uint)(u.Part1 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 4), (uint)(u.Part2 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 5), (uint)(u.Part2 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 6), (uint)(u.Part3 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 7), (uint)(u.Part3 >> 32)); + + return GetItem(p[..((UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32))]); + } + [Benchmark] + [ArgumentsSource(nameof(UInt256ParamsSource))] + public uint UInt256_Ref_WriteByUInt256(UInt256 u) + { + const int UIntCount = 32 / sizeof(uint); + Span p = stackalloc uint[UIntCount]; + + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), u); + + return GetItem(p[..((UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32))]); + } + + [Benchmark] + [ArgumentsSource(nameof(UInt512ParamsSource))] + public unsafe uint UInt512_UnsafePointer_WriteByUInt512(UInt512 u) + { + const int UIntCount = 64 / sizeof(uint); + + uint* p = stackalloc uint[UIntCount]; + Unsafe.WriteUnaligned(ref *(byte*)(p), u); + Span left = new Span(p, (UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32)); + + return GetItem(left); + } + [Benchmark] + [ArgumentsSource(nameof(UInt512ParamsSource))] + public unsafe uint UInt512_UnsafePointer_WriteByUInt32(UInt512 u) + { + const int UIntCount = 64 / sizeof(uint); + + uint* p = stackalloc uint[UIntCount]; + + Unsafe.WriteUnaligned(ref *(byte*)(p + 0), (uint)(u.Part0 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 1), (uint)(u.Part0 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 2), (uint)(u.Part1 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 3), (uint)(u.Part1 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 4), (uint)(u.Part2 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 5), (uint)(u.Part2 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 6), (uint)(u.Part3 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 7), (uint)(u.Part3 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 8), (uint)(u.Part4 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 9), (uint)(u.Part4 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 10), (uint)(u.Part5 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 11), (uint)(u.Part5 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 12), (uint)(u.Part6 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 13), (uint)(u.Part6 >> 32)); + + Unsafe.WriteUnaligned(ref *(byte*)(p + 14), (uint)(u.Part7 >> 00)); + Unsafe.WriteUnaligned(ref *(byte*)(p + 15), (uint)(u.Part7 >> 32)); + + Span left = new Span(p, (UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32)); + + return GetItem(left); + } + [Benchmark] + [ArgumentsSource(nameof(UInt512ParamsSource))] + public uint UInt512_Ref_WriteByUInt512(UInt512 u) + { + const int UIntCount = 64 / sizeof(uint); + + Span p = stackalloc uint[UIntCount]; + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), u); + + return GetItem(p[..((UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32))]); + } + [Benchmark] + [ArgumentsSource(nameof(UInt512ParamsSource))] + public uint UInt512_Ref_WriteByUInt32(UInt512 u) + { + const int UIntCount = 64 / sizeof(uint); + + Span p = stackalloc uint[UIntCount]; + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 0), (uint)(u.Part0 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 1), (uint)(u.Part0 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 2), (uint)(u.Part1 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 3), (uint)(u.Part1 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 4), (uint)(u.Part2 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 5), (uint)(u.Part2 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 6), (uint)(u.Part3 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 7), (uint)(u.Part3 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 8), (uint)(u.Part4 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 9), (uint)(u.Part4 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 10), (uint)(u.Part5 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 11), (uint)(u.Part5 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 12), (uint)(u.Part6 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 13), (uint)(u.Part6 >> 32)); + + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 14), (uint)(u.Part7 >> 00)); + Unsafe.WriteUnaligned(ref Unsafe.Add(ref Unsafe.As(ref MemoryMarshal.GetReference(p)), 15), (uint)(u.Part7 >> 32)); + + return GetItem(p[..((UIntCount) - (BitHelper.LeadingZeroCount(in u) / 32))]); + } + private uint GetItem(ReadOnlySpan span) + { + return span.IsEmpty ? span[_index] : 0; + } + public IEnumerable UInt256ParamsSource() + { + yield return (UInt256)uint.MaxValue; + yield return (UInt256)ulong.MaxValue; + yield return (UInt256)UInt128.MaxValue; + yield return (UInt256)UInt256.MaxValue; + } + public IEnumerable UInt512ParamsSource() + { + yield return (UInt512)uint.MaxValue; + yield return (UInt512)ulong.MaxValue; + yield return (UInt512)UInt128.MaxValue; + yield return (UInt512)UInt256.MaxValue; + yield return (UInt512)UInt512.MaxValue; + } +} + +public class ReadUnalignedBenchmarks +{ + private uint[] _array = Enumerable.Range(-2147483648, 64 / sizeof(uint)).Select(v => unchecked((uint)v)).ToArray(); + + [Benchmark] + public UInt256 UInt256_ReadUnaligned() + { + return Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(_array))); + } + [Benchmark] + public UInt256 UInt256_UnsafeAs() + { + return Unsafe.As(ref MemoryMarshal.GetArrayDataReference(_array)); + } + [Benchmark] + public UInt256 UInt256_Ctor_UseArray() + { + return new UInt256( + ((ulong)_array[7] << 32) | _array[6], ((ulong)_array[5] << 32) | _array[4], ((ulong)_array[3] << 32) | _array[2], ((ulong)_array[1] << 32) | _array[0] + ); + } + [Benchmark] + public UInt256 UInt256_Ctor_UseSpan() + { + ReadOnlySpan span = _array; + return new UInt256( + ((ulong)span[7] << 32) | span[6], ((ulong)span[5] << 32) | span[4], ((ulong)span[3] << 32) | span[2], ((ulong)span[1] << 32) | span[0] + ); + } + [Benchmark] + public UInt256 UInt256_Ctor_UseRef() + { + ref uint ptr = ref MemoryMarshal.GetArrayDataReference(_array); + return new UInt256( + ((ulong)Unsafe.Add(ref ptr, 7) << 32) | Unsafe.Add(ref ptr, 6), + ((ulong)Unsafe.Add(ref ptr, 5) << 32) | Unsafe.Add(ref ptr, 4), + ((ulong)Unsafe.Add(ref ptr, 3) << 32) | Unsafe.Add(ref ptr, 2), + ((ulong)Unsafe.Add(ref ptr, 1) << 32) | Unsafe.Add(ref ptr, 0) + ); + } + + [Benchmark] + public UInt512 UInt512_ReadUnaligned() + { + return Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetArrayDataReference(_array))); + } + [Benchmark] + public UInt512 UInt512_UnsafeAs() + { + return Unsafe.As(ref MemoryMarshal.GetArrayDataReference(_array)); + } + [Benchmark] + public UInt512 UInt512_Ctor_UseArray() + { + return new UInt512( + ((ulong)_array[15] << 32) | _array[14], ((ulong)_array[13] << 32) | _array[12], ((ulong)_array[11] << 32) | _array[10], ((ulong)_array[9] << 32) | _array[8], + ((ulong)_array[7] << 32) | _array[6], ((ulong)_array[5] << 32) | _array[4], ((ulong)_array[3] << 32) | _array[2], ((ulong)_array[1] << 32) | _array[0] + ); + } + [Benchmark] + public UInt512 UInt512_Ctor_UseSpan() + { + ReadOnlySpan span = _array; + return new UInt512( + ((ulong)span[15] << 32) | span[14], ((ulong)span[13] << 32) | span[12], ((ulong)span[11] << 32) | span[10], ((ulong)span[9] << 32) | span[8], + ((ulong)span[7] << 32) | span[6], ((ulong)span[5] << 32) | span[4], ((ulong)span[3] << 32) | span[2], ((ulong)span[1] << 32) | span[0] + ); + } + [Benchmark] + public UInt512 UInt512_Ctor_UseRef() + { + ref uint ptr = ref MemoryMarshal.GetArrayDataReference(_array); + return new UInt512( + ((ulong)Unsafe.Add(ref ptr, 15) << 32) | Unsafe.Add(ref ptr, 14), + ((ulong)Unsafe.Add(ref ptr, 13) << 32) | Unsafe.Add(ref ptr, 12), + ((ulong)Unsafe.Add(ref ptr, 11) << 32) | Unsafe.Add(ref ptr, 10), + ((ulong)Unsafe.Add(ref ptr, 9) << 32) | Unsafe.Add(ref ptr, 8), + ((ulong)Unsafe.Add(ref ptr, 7) << 32) | Unsafe.Add(ref ptr, 6), + ((ulong)Unsafe.Add(ref ptr, 5) << 32) | Unsafe.Add(ref ptr, 4), + ((ulong)Unsafe.Add(ref ptr, 3) << 32) | Unsafe.Add(ref ptr, 2), + ((ulong)Unsafe.Add(ref ptr, 1) << 32) | Unsafe.Add(ref ptr, 0) + ); + } +} \ No newline at end of file diff --git a/src/MissingValues.Benchmarks/MissingValues.Benchmarks.csproj b/src/MissingValues.Benchmarks/MissingValues.Benchmarks.csproj index f06287d..01eab71 100644 --- a/src/MissingValues.Benchmarks/MissingValues.Benchmarks.csproj +++ b/src/MissingValues.Benchmarks/MissingValues.Benchmarks.csproj @@ -2,13 +2,14 @@ Exe - net8.0 + net9.0 enable enable + true - + diff --git a/src/MissingValues.Benchmarks/Program.cs b/src/MissingValues.Benchmarks/Program.cs index 402a1c9..1875a3f 100644 --- a/src/MissingValues.Benchmarks/Program.cs +++ b/src/MissingValues.Benchmarks/Program.cs @@ -13,10 +13,12 @@ #if DEBUG -Console.WriteLine(Quad.Sqrt(10)); -Console.WriteLine(Octo.Sqrt(10)); +UInt512 b = UInt512.MaxValue / UInt512.Parse("10000000000000000000000000000000000000000"); +UInt256 a = UInt256.Parse("100000000000000000000000000000000000000"); +UInt512 c = UInt512.Parse("13407807929942597099574024998205846127479365820592393377723561443721764030073"); #else + BenchmarkSwitcher.FromTypes( [ typeof(UInt256Benchmarks.MathOperators), @@ -30,8 +32,11 @@ typeof(QuadBenchmarks.MathOperators), typeof(QuadBenchmarks.Parsing), typeof(GenericIntegerBenchmarks<>), - typeof(BigIntegerBenchmarks) + typeof(BigIntegerBenchmarks), + typeof(WriteUnalignedBenchmarks), + typeof(ReadUnalignedBenchmarks) ] - ).Run(args); + ).Run(args); + #endif Console.ReadLine(); \ No newline at end of file diff --git a/src/MissingValues.Benchmarks/SlowDivisionBenchmarks.cs b/src/MissingValues.Benchmarks/SlowDivisionBenchmarks.cs index c586b0e..596c968 100644 --- a/src/MissingValues.Benchmarks/SlowDivisionBenchmarks.cs +++ b/src/MissingValues.Benchmarks/SlowDivisionBenchmarks.cs @@ -54,15 +54,6 @@ public void Setup() } - [Benchmark] - public ulong[][] SlowDivision_UInt64() - { - for (int i = 0; i < Length; i++) - { - Calculator.Divide(a64[i], b64[i], c64[i]); - } - return c64; - } [Benchmark] public uint[][] SlowDivision_UInt32() { diff --git a/src/MissingValues.Tests/Core/Int256Test.GenericMath.cs b/src/MissingValues.Tests/Core/Int256Test.GenericMath.cs index 72bd699..f4302f4 100644 --- a/src/MissingValues.Tests/Core/Int256Test.GenericMath.cs +++ b/src/MissingValues.Tests/Core/Int256Test.GenericMath.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; @@ -842,6 +843,10 @@ public static void CreateCheckedToInt256Test() NumberBaseHelper.CreateChecked(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateChecked(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateChecked(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")) + .Should().Be(MaxValue); NumberBaseHelper.CreateChecked(Int256MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateChecked(byte.MinValue).Should().Be(Zero); @@ -849,6 +854,10 @@ public static void CreateCheckedToInt256Test() NumberBaseHelper.CreateChecked(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateChecked(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateChecked(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")) + .Should().Be(MinValue); NumberBaseHelper.CreateChecked(Int256MinValueAsDouble).Should().Be(MinValue); } [Fact] @@ -859,6 +868,10 @@ public static void CreateSaturatingToInt256Test() NumberBaseHelper.CreateSaturating(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateSaturating(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateSaturating(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")) + .Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(Int256MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(byte.MinValue).Should().Be(Zero); @@ -866,6 +879,10 @@ public static void CreateSaturatingToInt256Test() NumberBaseHelper.CreateSaturating(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateSaturating(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateSaturating(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")) + .Should().Be(MinValue); NumberBaseHelper.CreateSaturating(Int256MinValueAsDouble).Should().Be(MinValue); } [Fact] @@ -876,6 +893,10 @@ public static void CreateTruncatingToInt256Test() NumberBaseHelper.CreateTruncating(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateTruncating(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateTruncating(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")) + .Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(Int256MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(byte.MinValue).Should().Be(Zero); @@ -883,6 +904,10 @@ public static void CreateTruncatingToInt256Test() NumberBaseHelper.CreateTruncating(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateTruncating(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateTruncating(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")) + .Should().Be(MinValue); NumberBaseHelper.CreateTruncating(Int256MinValueAsDouble).Should().Be(MinValue); } @@ -894,6 +919,9 @@ public static void CreateCheckedFromInt256Test() NumberBaseHelper.CreateChecked(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateChecked(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateChecked(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateChecked(MaxValue).Should() + .Be(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")); NumberBaseHelper.CreateChecked(MaxValue).Should().Be(Int256MaxValueAsDouble); NumberBaseHelper.CreateChecked(Zero).Should().Be(byte.MinValue); @@ -901,6 +929,9 @@ public static void CreateCheckedFromInt256Test() NumberBaseHelper.CreateChecked(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateChecked(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateChecked(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateChecked(MinValue).Should() + .Be(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")); NumberBaseHelper.CreateChecked(MinValue).Should().Be(Int256MinValueAsDouble); } [Fact] @@ -911,6 +942,9 @@ public static void CreateSaturatingFromInt256Test() NumberBaseHelper.CreateSaturating(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateSaturating(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateSaturating(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateSaturating(MaxValue).Should() + .Be(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")); NumberBaseHelper.CreateSaturating(MaxValue).Should().Be(Int256MaxValueAsDouble); NumberBaseHelper.CreateSaturating(Zero).Should().Be(byte.MinValue); @@ -918,6 +952,9 @@ public static void CreateSaturatingFromInt256Test() NumberBaseHelper.CreateSaturating(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateSaturating(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateSaturating(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateSaturating(MinValue).Should() + .Be(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")); NumberBaseHelper.CreateSaturating(MinValue).Should().Be(Int256MinValueAsDouble); } [Fact] @@ -928,6 +965,9 @@ public static void CreateTruncatingFromInt256Test() NumberBaseHelper.CreateTruncating(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateTruncating(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateTruncating(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateTruncating(MaxValue).Should() + .Be(BigInteger.Parse( + "57896044618658097711785492504343953926634992332820282019728792003956564819967")); NumberBaseHelper.CreateTruncating(MaxValue).Should().Be(Int256MaxValueAsDouble); NumberBaseHelper.CreateTruncating(Zero).Should().Be(byte.MinValue); @@ -935,6 +975,9 @@ public static void CreateTruncatingFromInt256Test() NumberBaseHelper.CreateTruncating(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateTruncating(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateTruncating(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateTruncating(MinValue).Should() + .Be(BigInteger.Parse( + "-57896044618658097711785492504343953926634992332820282019728792003956564819968")); NumberBaseHelper.CreateTruncating(MinValue).Should().Be(Int256MinValueAsDouble); } @@ -1180,5 +1223,25 @@ public static void NegativeOneTest() MathConstantsHelper.NegativeOne().Should().Be(NegativeOne); } #endregion + + #region IPowerFunctions + [Fact] + public void PowTest() + { + GenericFloatingPointFunctions.Pow(Zero, int.MaxValue).Should().Be(Zero); + GenericFloatingPointFunctions.Pow(One, int.MaxValue).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, Zero).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, One).Should().Be(MaxValue); + GenericFloatingPointFunctions.Pow(Two, Two).Should().Be(4); + GenericFloatingPointFunctions.Pow(Two, 4).Should().Be(16); + GenericFloatingPointFunctions.Pow(16, Two).Should().Be(256); + GenericFloatingPointFunctions.Pow(Two, 254) + .Should().Be(new Int(0x4000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)); + + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, NegativeOne)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, 255)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two + Two, 254)); + } + #endregion } } diff --git a/src/MissingValues.Tests/Core/Int256Test.cs b/src/MissingValues.Tests/Core/Int256Test.cs index 46fae09..1fc9f96 100644 --- a/src/MissingValues.Tests/Core/Int256Test.cs +++ b/src/MissingValues.Tests/Core/Int256Test.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Text.Unicode; @@ -73,6 +74,16 @@ public void Cast_ToInt128() Int128.MaxValue.Should().Be((Int128)Int128MaxValue); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.One.Should().Be((BigInteger)One); + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967") + .Should().Be((BigInteger)MaxValue); + BigInteger.Parse("-57896044618658097711785492504343953926634992332820282019728792003956564819968") + .Should().Be((BigInteger)MinValue); + } + [Fact] public void Cast_ToDouble() { diff --git a/src/MissingValues.Tests/Core/Int512Test.GenericMath.cs b/src/MissingValues.Tests/Core/Int512Test.GenericMath.cs index 1412fba..e761374 100644 --- a/src/MissingValues.Tests/Core/Int512Test.GenericMath.cs +++ b/src/MissingValues.Tests/Core/Int512Test.GenericMath.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; @@ -720,6 +721,10 @@ public static void CreateCheckedToInt512Test() NumberBaseHelper.CreateChecked(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateChecked(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateChecked(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")) + .Should().Be(MaxValue); NumberBaseHelper.CreateChecked((double)int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateChecked(byte.MinValue).Should().Be(Zero); @@ -727,6 +732,10 @@ public static void CreateCheckedToInt512Test() NumberBaseHelper.CreateChecked(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateChecked(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateChecked(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")) + .Should().Be(MinValue); NumberBaseHelper.CreateChecked((double)int.MinValue).Should().Be(Int32MinValue); } [Fact] @@ -737,6 +746,10 @@ public static void CreateSaturatingToInt512Test() NumberBaseHelper.CreateSaturating(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateSaturating(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateSaturating(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")) + .Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(Int512MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(byte.MinValue).Should().Be(Zero); @@ -744,6 +757,10 @@ public static void CreateSaturatingToInt512Test() NumberBaseHelper.CreateSaturating(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateSaturating(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateSaturating(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")) + .Should().Be(MinValue); NumberBaseHelper.CreateSaturating(Int512MinValueAsDouble).Should().Be(MinValue); } [Fact] @@ -754,6 +771,10 @@ public static void CreateTruncatingToInt512Test() NumberBaseHelper.CreateTruncating(int.MaxValue).Should().Be(Int32MaxValue); NumberBaseHelper.CreateTruncating(long.MaxValue).Should().Be(Int64MaxValue); NumberBaseHelper.CreateTruncating(Int128.MaxValue).Should().Be(Int128MaxValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")) + .Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(Int512MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(byte.MinValue).Should().Be(Zero); @@ -761,6 +782,10 @@ public static void CreateTruncatingToInt512Test() NumberBaseHelper.CreateTruncating(int.MinValue).Should().Be(Int32MinValue); NumberBaseHelper.CreateTruncating(long.MinValue).Should().Be(Int64MinValue); NumberBaseHelper.CreateTruncating(Int128.MinValue).Should().Be(Int128MinValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")) + .Should().Be(MinValue); NumberBaseHelper.CreateTruncating(Int512MinValueAsDouble).Should().Be(MinValue); } @@ -772,6 +797,9 @@ public static void CreateCheckedFromInt512Test() NumberBaseHelper.CreateChecked(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateChecked(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateChecked(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateChecked(MaxValue).Should() + .Be(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")); NumberBaseHelper.CreateChecked(MaxValue).Should().Be(Int512MaxValueAsDouble); NumberBaseHelper.CreateChecked(Zero).Should().Be(byte.MinValue); @@ -779,6 +807,9 @@ public static void CreateCheckedFromInt512Test() NumberBaseHelper.CreateChecked(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateChecked(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateChecked(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateChecked(MinValue).Should() + .Be(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")); NumberBaseHelper.CreateChecked(MinValue).Should().Be(Int512MinValueAsDouble); } [Fact] @@ -789,6 +820,9 @@ public static void CreateSaturatingFromInt512Test() NumberBaseHelper.CreateSaturating(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateSaturating(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateSaturating(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateSaturating(MaxValue).Should() + .Be(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")); NumberBaseHelper.CreateSaturating(MaxValue).Should().Be(Int512MaxValueAsDouble); NumberBaseHelper.CreateSaturating(Zero).Should().Be(byte.MinValue); @@ -796,6 +830,9 @@ public static void CreateSaturatingFromInt512Test() NumberBaseHelper.CreateSaturating(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateSaturating(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateSaturating(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateSaturating(MinValue).Should() + .Be(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")); NumberBaseHelper.CreateSaturating(MinValue).Should().Be(Int512MinValueAsDouble); } [Fact] @@ -806,6 +843,9 @@ public static void CreateTruncatingFromInt512Test() NumberBaseHelper.CreateTruncating(Int32MaxValue).Should().Be(int.MaxValue); NumberBaseHelper.CreateTruncating(Int64MaxValue).Should().Be(long.MaxValue); NumberBaseHelper.CreateTruncating(Int128MaxValue).Should().Be(Int128.MaxValue); + NumberBaseHelper.CreateTruncating(MaxValue).Should() + .Be(BigInteger.Parse( + "6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047")); NumberBaseHelper.CreateTruncating(MaxValue).Should().Be(Int512MaxValueAsDouble); NumberBaseHelper.CreateTruncating(Zero).Should().Be(byte.MinValue); @@ -813,6 +853,9 @@ public static void CreateTruncatingFromInt512Test() NumberBaseHelper.CreateTruncating(Int32MinValue).Should().Be(int.MinValue); NumberBaseHelper.CreateTruncating(Int64MinValue).Should().Be(long.MinValue); NumberBaseHelper.CreateTruncating(Int128MinValue).Should().Be(Int128.MinValue); + NumberBaseHelper.CreateTruncating(MinValue).Should() + .Be(BigInteger.Parse( + "-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")); NumberBaseHelper.CreateTruncating(MinValue).Should().Be(Int512MinValueAsDouble); } @@ -1114,5 +1157,27 @@ public static void NegativeOneTest() MathConstantsHelper.NegativeOne().Should().Be(NegativeOne); } #endregion + + #region IPowerFunctions + [Fact] + public void PowTest() + { + GenericFloatingPointFunctions.Pow(Zero, int.MaxValue).Should().Be(Zero); + GenericFloatingPointFunctions.Pow(One, int.MaxValue).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, Zero).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, One).Should().Be(MaxValue); + GenericFloatingPointFunctions.Pow(Two, Two).Should().Be(4); + GenericFloatingPointFunctions.Pow(Two, 4).Should().Be(16); + GenericFloatingPointFunctions.Pow(16, Two).Should().Be(256); + GenericFloatingPointFunctions.Pow(Two, 510) + .Should().Be(new Int( + 0x4000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)); + + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, NegativeOne)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, 511)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two + Two, 510)); + } + #endregion } } diff --git a/src/MissingValues.Tests/Core/Int512Test.cs b/src/MissingValues.Tests/Core/Int512Test.cs index 79cfb19..9633022 100644 --- a/src/MissingValues.Tests/Core/Int512Test.cs +++ b/src/MissingValues.Tests/Core/Int512Test.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Text.Unicode; @@ -80,6 +81,16 @@ public void Cast_ToInt256() Int256.MaxValue.Should().Be((Int256)Int256MaxValue); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.One.Should().Be((BigInteger)One); + BigInteger.Parse("6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042047") + .Should().Be((BigInteger)MaxValue); + BigInteger.Parse("-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048") + .Should().Be((BigInteger)MinValue); + } + [Fact] public void Cast_ToDouble() { diff --git a/src/MissingValues.Tests/Core/NumberFormatTest.cs b/src/MissingValues.Tests/Core/NumberFormatTest.cs index 0554c6c..7200d85 100644 --- a/src/MissingValues.Tests/Core/NumberFormatTest.cs +++ b/src/MissingValues.Tests/Core/NumberFormatTest.cs @@ -1,4 +1,5 @@ -using System; +using MissingValues.Tests.Helpers; +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -9,7 +10,7 @@ namespace MissingValues.Tests.Core { public class NumberFormatTest { - private static readonly Quad _decimalSampleValue = Values.CreateFloat(0x400C_81CD_6E63_1F8A, 0x0902_DE00_D1B7_1759); + private static readonly Quad _decimalSampleValue = Values.CreateFloat(0x400C_81CD_6E63_1F8A, 0x0902_DE00_D1B7_1759); // 12345.6789 private static readonly NumberFormatInfo CustomInfo = new() { PositiveSign = "+", @@ -50,9 +51,34 @@ private static readonly (IFormattable, string, NumberFormatInfo?, string)[] _for (Int512.MinValue, "C", CustomInfo, "$-6,703,903,964,971,298,549,787,012,499,102,923,063,739,682,910,296,196,688,861,780,721,860,882,015,036,773,488,400,937,149,083,451,713,845,015,929,093,243,025,426,876,941,405,973,284,973,216,824,503,042,048.00"), (_decimalSampleValue, "C", CustomInfo, "$12,345.68"), ]; + private static readonly (string, NumberStyles, NumberFormatInfo?, Int512, bool)[] _parseInt512 = + [ + ("1E200", NumberStyles.Number | NumberStyles.AllowExponent, NumberFormatInfo.CurrentInfo, default, false), + ("2,5E10", NumberStyles.Number | NumberStyles.AllowExponent, NumberFormatInfo.CurrentInfo, 25_000_000_000, true), + ("1E10", NumberStyles.Number | NumberStyles.AllowExponent, NumberFormatInfo.CurrentInfo, 10_000_000_000, true), + ("1,000", NumberStyles.Number, NumberFormatInfo.CurrentInfo, Int512.One, true), + ("1.000,0", NumberStyles.Number, NumberFormatInfo.CurrentInfo, 1_000, true), + ("1.000.000", NumberStyles.Number, NumberFormatInfo.CurrentInfo, 1_000_000, true), + ("1.000.000.000,00", NumberStyles.Number, NumberFormatInfo.CurrentInfo, 1_000_000_000, true), + ("-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048.000", NumberStyles.Number, NumberFormatInfo.InvariantInfo, Int512.MinValue, true), + ("-6,703,903,964,971,298,549,787,012,499,102,923,063,739,682,910,296,196,688,861,780,721,860,882,015,036,773,488,400,937,149,083,451,713,845,015,929,093,243,025,426,876,941,405,973,284,973,216,824,503,042,048", NumberStyles.Number, NumberFormatInfo.InvariantInfo, Int512.MinValue, true), + ("$-6,703,903,964,971,298,549,787,012,499,102,923,063,739,682,910,296,196,688,861,780,721,860,882,015,036,773,488,400,937,149,083,451,713,845,015,929,093,243,025,426,876,941,405,973,284,973,216,824,503,042,048.00", NumberStyles.Currency, CustomInfo, Int512.MinValue, true), + ]; + private static readonly (string, NumberStyles, NumberFormatInfo?, Quad, bool)[] _parseQuad = + [ + ("2,5E-1", NumberStyles.Float, NumberFormatInfo.CurrentInfo, QuadTest.Quarter, true), + ("0,250", NumberStyles.Float, NumberFormatInfo.CurrentInfo, QuadTest.Quarter, true), + ("$-0.25", NumberStyles.Currency, CustomInfo, QuadTest.NegativeQuarter, true), + ("1,000", NumberStyles.Float, NumberFormatInfo.CurrentInfo, Quad.One, true), + ("1.000,0", NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, QuadTest.Thousand, true), + ("-1.000,0", NumberStyles.Float | NumberStyles.AllowThousands, NumberFormatInfo.CurrentInfo, QuadTest.NegativeThousand, true), + ]; public static readonly FormatStringTheoryData FormatsTheoryData = new(_formats); + public static readonly FormatParsingTheoryData ParseInt512TheoryData = new(_parseInt512); + public static readonly FormatParsingTheoryData ParseQuadTheoryData = new(_parseQuad); + [Theory] [MemberData(nameof(FormatsTheoryData))] public void FormattingTest(IFormattable value, string fmt, NumberFormatInfo? info, string expected) @@ -60,5 +86,19 @@ public void FormattingTest(IFormattable value, string fmt, NumberFormatInfo? inf string actual = value.ToString(fmt, info); actual.Should().Be(expected); } + [Theory] + [MemberData(nameof(ParseInt512TheoryData))] + public void IntegerParsingTest(string s, NumberStyles style, NumberFormatInfo? info, Int512 expected, bool success) + { + Int512.TryParse(s, style, info, out Int512 actual).Should().Be(success); + actual.Should().Be(expected); + } + [Theory] + [MemberData(nameof(ParseQuadTheoryData))] + public void FloatingPointParsingTest(string s, NumberStyles style, NumberFormatInfo? info, Quad expected, bool success) + { + Quad.TryParse(s, style, info, out Quad actual).Should().Be(success); + actual.Should().Be(expected); + } } } diff --git a/src/MissingValues.Tests/Core/OctoTest.cs b/src/MissingValues.Tests/Core/OctoTest.cs index ef04f6c..5002b14 100644 --- a/src/MissingValues.Tests/Core/OctoTest.cs +++ b/src/MissingValues.Tests/Core/OctoTest.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Threading.Tasks; @@ -156,6 +157,15 @@ public void Cast_FromInt512(Int512 from, Octo to) { ((Octo)from).Should().Be(to); } + [Fact] + public void Cast_FromBigInteger() + { + TwoOver255.Should() + .Be((Octo)BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819968")); + TwoOver511 + .Should() + .Be((Octo)BigInteger.Parse("6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")); + } [Theory] [MemberData(nameof(CastFromHalfTheoryData))] public void Cast_FromHalf(Half from, Octo to) @@ -259,6 +269,14 @@ public void Cast_ToInt512(Octo from, Int512 to) { ((Int512)from).Should().Be(to); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819968") + .Should().Be((BigInteger)TwoOver255); + BigInteger.Parse("6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048") + .Should().Be((BigInteger)TwoOver511); + } [Theory] [MemberData(nameof(CastToHalfTheoryData))] public void Cast_ToHalf(Octo from, Half to) diff --git a/src/MissingValues.Tests/Core/QuadTest.cs b/src/MissingValues.Tests/Core/QuadTest.cs index b7e0bd4..8abefb7 100644 --- a/src/MissingValues.Tests/Core/QuadTest.cs +++ b/src/MissingValues.Tests/Core/QuadTest.cs @@ -4,6 +4,7 @@ using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Text.Unicode; @@ -160,6 +161,17 @@ public void Cast_FromInt512(Int512 from, Quad to) { ((Quad)from).Should().Be(to); } + [Fact] + public void Cast_FromBigInteger() + { + TwoOver127.Should() + .Be((Quad)BigInteger.Parse("170141183460469231731687303715884105728")); + TwoOver255.Should() + .Be((Quad)BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819968")); + TwoOver511 + .Should() + .Be((Quad)BigInteger.Parse("6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048")); + } [Theory] [MemberData(nameof(CastFromHalfTheoryData))] public void Cast_FromHalf(Half from, Quad to) @@ -263,6 +275,16 @@ public void Cast_ToInt512(Quad from, Int512 to) { ((Int512)from).Should().Be(to); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.Parse("170141183460469231731687303715884105728") + .Should().Be((BigInteger)TwoOver127); + BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819968") + .Should().Be((BigInteger)TwoOver255); + BigInteger.Parse("6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048") + .Should().Be((BigInteger)TwoOver511); + } [Theory] [MemberData(nameof(CastToHalfTheoryData))] public void Cast_ToHalf(Quad from, Half to) diff --git a/src/MissingValues.Tests/Core/UInt256Test.GenericMath.cs b/src/MissingValues.Tests/Core/UInt256Test.GenericMath.cs index 63aed56..cf22105 100644 --- a/src/MissingValues.Tests/Core/UInt256Test.GenericMath.cs +++ b/src/MissingValues.Tests/Core/UInt256Test.GenericMath.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; @@ -778,6 +779,10 @@ public static void CreateCheckedToUInt256Test() NumberBaseHelper.CreateChecked(uint.MaxValue).Should().Be(UInt32MaxValue); NumberBaseHelper.CreateChecked(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateChecked(UInt128.MaxValue).Should().Be(UInt128MaxValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")) + .Should().Be(MaxValue); NumberBaseHelper.CreateChecked(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateChecked(byte.MinValue).Should().Be(Zero); @@ -794,6 +799,10 @@ public static void CreateSaturatingToUInt256Test() NumberBaseHelper.CreateSaturating(uint.MaxValue).Should().Be(UInt32MaxValue); NumberBaseHelper.CreateSaturating(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateSaturating(UInt128.MaxValue).Should().Be(UInt128MaxValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")) + .Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(byte.MinValue).Should().Be(Zero); @@ -810,6 +819,10 @@ public static void CreateTruncatingToUInt256Test() NumberBaseHelper.CreateTruncating(uint.MaxValue).Should().Be(UInt32MaxValue); NumberBaseHelper.CreateTruncating(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateTruncating(UInt128.MaxValue).Should().Be(UInt128MaxValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")) + .Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(byte.MinValue).Should().Be(Zero); @@ -827,6 +840,9 @@ public static void CreateCheckedFromUInt256Test() NumberBaseHelper.CreateChecked(UInt32MaxValue).Should().Be(uint.MaxValue); NumberBaseHelper.CreateChecked(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateChecked(UInt128MaxValue).Should().Be(UInt128.MaxValue); + NumberBaseHelper.CreateChecked(MaxValue).Should() + .Be(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")); NumberBaseHelper.CreateChecked(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateChecked(Zero).Should().Be(byte.MinValue); @@ -843,6 +859,9 @@ public static void CreateSaturatingFromUInt256Test() NumberBaseHelper.CreateSaturating(UInt32MaxValue).Should().Be(uint.MaxValue); NumberBaseHelper.CreateSaturating(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateSaturating(UInt128MaxValue).Should().Be(UInt128.MaxValue); + NumberBaseHelper.CreateSaturating(MaxValue).Should() + .Be(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")); NumberBaseHelper.CreateSaturating(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateSaturating(Zero).Should().Be(byte.MinValue); @@ -859,6 +878,9 @@ public static void CreateTruncatingFromUInt256Test() NumberBaseHelper.CreateTruncating(UInt32MaxValue).Should().Be(uint.MaxValue); NumberBaseHelper.CreateTruncating(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateTruncating(UInt128MaxValue).Should().Be(UInt128.MaxValue); + NumberBaseHelper.CreateTruncating(MaxValue).Should() + .Be(BigInteger.Parse( + "115792089237316195423570985008687907853269984665640564039457584007913129639935")); NumberBaseHelper.CreateTruncating(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateTruncating(Zero).Should().Be(byte.MinValue); @@ -1067,5 +1089,24 @@ public void TryParseUtf8Test() parsedValue.Should().Be(default); } #endregion + + #region IPowerFunctions + [Fact] + public void PowTest() + { + GenericFloatingPointFunctions.Pow(Zero, (uint)int.MaxValue).Should().Be(Zero); + GenericFloatingPointFunctions.Pow(One, (uint)int.MaxValue).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, Zero).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, One).Should().Be(MaxValue); + GenericFloatingPointFunctions.Pow(Two, Two).Should().Be(4); + GenericFloatingPointFunctions.Pow(Two, 4U).Should().Be(16); + GenericFloatingPointFunctions.Pow(16U, Two).Should().Be(256); + GenericFloatingPointFunctions.Pow(Two, 255U) + .Should().Be(new UInt(0x8000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)); + + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, 256U)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two + Two, 255U)); + } + #endregion } } diff --git a/src/MissingValues.Tests/Core/UInt256Test.cs b/src/MissingValues.Tests/Core/UInt256Test.cs index 8aaeee2..d504f5e 100644 --- a/src/MissingValues.Tests/Core/UInt256Test.cs +++ b/src/MissingValues.Tests/Core/UInt256Test.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Text.Unicode; @@ -48,6 +49,14 @@ public void Cast_ToUInt128() UInt128.MaxValue.Should().Be((UInt128)UInt128MaxValue); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.One.Should().Be((BigInteger)One); + BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007913129639935") + .Should().Be((BigInteger)MaxValue); + } + [Fact] public void Cast_ToDouble() { diff --git a/src/MissingValues.Tests/Core/UInt512Test.GenericMath.cs b/src/MissingValues.Tests/Core/UInt512Test.GenericMath.cs index b26fb64..833feb6 100644 --- a/src/MissingValues.Tests/Core/UInt512Test.GenericMath.cs +++ b/src/MissingValues.Tests/Core/UInt512Test.GenericMath.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Threading.Tasks; @@ -764,6 +765,10 @@ public static void CreateCheckedToUInt512Test() NumberBaseHelper.CreateChecked(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateChecked(UInt128.MaxValue).Should().Be(UInt128MaxValue); NumberBaseHelper.CreateChecked(UInt256.MaxValue).Should().Be(UInt256MaxValue); + NumberBaseHelper + .CreateChecked(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")) + .Should().Be(MaxValue); NumberBaseHelper.CreateChecked(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateChecked(byte.MinValue).Should().Be(Zero); @@ -782,6 +787,10 @@ public static void CreateSaturatingToUInt512Test() NumberBaseHelper.CreateSaturating(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateSaturating(UInt128.MaxValue).Should().Be(UInt128MaxValue); NumberBaseHelper.CreateSaturating(UInt256.MaxValue).Should().Be(UInt256MaxValue); + NumberBaseHelper + .CreateSaturating(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")) + .Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateSaturating(byte.MinValue).Should().Be(Zero); @@ -800,6 +809,10 @@ public static void CreateTruncatingToUInt512Test() NumberBaseHelper.CreateTruncating(ulong.MaxValue).Should().Be(UInt64MaxValue); NumberBaseHelper.CreateTruncating(UInt128.MaxValue).Should().Be(UInt128MaxValue); NumberBaseHelper.CreateTruncating(UInt256.MaxValue).Should().Be(UInt256MaxValue); + NumberBaseHelper + .CreateTruncating(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")) + .Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(MaxValueAsDouble).Should().Be(MaxValue); NumberBaseHelper.CreateTruncating(byte.MinValue).Should().Be(Zero); @@ -819,6 +832,9 @@ public static void CreateCheckedFromUInt512Test() NumberBaseHelper.CreateChecked(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateChecked(UInt128MaxValue).Should().Be(UInt128.MaxValue); NumberBaseHelper.CreateChecked(UInt256MaxValue).Should().Be(UInt256.MaxValue); + NumberBaseHelper.CreateChecked(MaxValue).Should() + .Be(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")); NumberBaseHelper.CreateChecked(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateChecked(Zero).Should().Be(byte.MinValue); @@ -837,6 +853,9 @@ public static void CreateSaturatingFromUInt512Test() NumberBaseHelper.CreateSaturating(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateSaturating(UInt128MaxValue).Should().Be(UInt128.MaxValue); NumberBaseHelper.CreateSaturating(UInt256MaxValue).Should().Be(UInt256.MaxValue); + NumberBaseHelper.CreateSaturating(MaxValue).Should() + .Be(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")); NumberBaseHelper.CreateSaturating(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateSaturating(Zero).Should().Be(byte.MinValue); @@ -855,6 +874,9 @@ public static void CreateTruncatingFromUInt512Test() NumberBaseHelper.CreateTruncating(UInt64MaxValue).Should().Be(ulong.MaxValue); NumberBaseHelper.CreateTruncating(UInt128MaxValue).Should().Be(UInt128.MaxValue); NumberBaseHelper.CreateTruncating(UInt256MaxValue).Should().Be(UInt256.MaxValue); + NumberBaseHelper.CreateTruncating(MaxValue).Should() + .Be(BigInteger.Parse( + "13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095")); NumberBaseHelper.CreateTruncating(MaxValue).Should().Be(MaxValueAsDouble); NumberBaseHelper.CreateTruncating(Zero).Should().Be(byte.MinValue); @@ -1064,5 +1086,26 @@ public void TryParseUtf8Test() parsedValue.Should().Be(default); } #endregion + + #region IPowerFunctions + [Fact] + public void PowTest() + { + GenericFloatingPointFunctions.Pow(Zero, (uint)int.MaxValue).Should().Be(Zero); + GenericFloatingPointFunctions.Pow(One, (uint)int.MaxValue).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, Zero).Should().Be(One); + GenericFloatingPointFunctions.Pow(MaxValue, One).Should().Be(MaxValue); + GenericFloatingPointFunctions.Pow(Two, Two).Should().Be(4); + GenericFloatingPointFunctions.Pow(Two, 4U).Should().Be(16); + GenericFloatingPointFunctions.Pow(16U, Two).Should().Be(256); + GenericFloatingPointFunctions.Pow(Two, 511U) + .Should().Be(new UInt( + 0x8000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, + 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)); + + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two, 512U)); + Assert.Throws(() => GenericFloatingPointFunctions.Pow(Two + Two, 511U)); + } + #endregion } } diff --git a/src/MissingValues.Tests/Core/UInt512Test.cs b/src/MissingValues.Tests/Core/UInt512Test.cs index 180ae9b..9838890 100644 --- a/src/MissingValues.Tests/Core/UInt512Test.cs +++ b/src/MissingValues.Tests/Core/UInt512Test.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Numerics; using System.Text; using System.Text.Json; using System.Text.Unicode; @@ -55,6 +56,14 @@ public void Cast_ToUInt256() UInt256.MaxValue.Should().Be((UInt256)UInt256MaxValue); } + [Fact] + public void Cast_ToBigInteger() + { + BigInteger.One.Should().Be((BigInteger)One); + BigInteger.Parse("13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095") + .Should().Be((BigInteger)MaxValue); + } + [Fact] public void Cast_ToDouble() { diff --git a/src/MissingValues.Tests/Helpers/TheoryDataTypes.cs b/src/MissingValues.Tests/Helpers/TheoryDataTypes.cs index a626a0d..29ef7b8 100644 --- a/src/MissingValues.Tests/Helpers/TheoryDataTypes.cs +++ b/src/MissingValues.Tests/Helpers/TheoryDataTypes.cs @@ -129,4 +129,17 @@ public FormatStringTheoryData(IEnumerable<(IFormattable, string, NumberFormatInf } } } + public class FormatParsingTheoryData : TheoryData + where TNumber : INumber + { + public FormatParsingTheoryData(IEnumerable<(string, NumberStyles, NumberFormatInfo?, TNumber, bool)> data) + { + Contract.Assert(data is not null && data.Any()); + + foreach (var dat in data) + { + Add(dat.Item1, dat.Item2, dat.Item3, dat.Item4, dat.Item5); + } + } + } } diff --git a/src/MissingValues.Tests/MissingValues.Tests.csproj b/src/MissingValues.Tests/MissingValues.Tests.csproj index 663c3a6..8433ee1 100644 --- a/src/MissingValues.Tests/MissingValues.Tests.csproj +++ b/src/MissingValues.Tests/MissingValues.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net9.0 enable enable @@ -10,14 +10,14 @@ - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/src/MissingValues/Info/NumberFormatter.Integer.cs b/src/MissingValues/Info/NumberFormatter.Integer.cs index 8d3cccd..af25def 100644 --- a/src/MissingValues/Info/NumberFormatter.Integer.cs +++ b/src/MissingValues/Info/NumberFormatter.Integer.cs @@ -28,6 +28,8 @@ internal interface IFormattableInteger : IFormattableNumber, IBigI /// The hexadecimal value of if it represents a number; otherwise, 0 abstract static TSelf GetHexValue(char value); + abstract static int UnsignedCompare(in TSelf value1, in TSelf value2); + static bool IFormattableNumber.IsBinaryInteger() => true; /// @@ -85,6 +87,7 @@ internal interface IFormattableInteger : IFormattableNumber, IBigI /// Gets the number of digits of the maximum binary value of . /// abstract static int MaxBinaryDigits { get; } + abstract static bool IsUnsignedInteger { get; } } internal interface IFormattableSignedInteger : IFormattableInteger, ISignedNumber @@ -96,6 +99,8 @@ internal interface IFormattableSignedInteger : IFormattableI /// /// The unsigned representation of the signed integer. TUnsigned ToUnsigned(); + + static bool IFormattableInteger.IsUnsignedInteger => false; } internal interface IFormattableUnsignedInteger : IFormattableInteger, IUnsignedNumber @@ -113,6 +118,8 @@ internal interface IFormattableUnsignedInteger : IFormattabl /// The signed representation of the unsigned integer. TSigned ToSigned(); static abstract int CountDigits(in TUnsigned value); + + static bool IFormattableInteger.IsUnsignedInteger => true; } internal static partial class NumberFormatter diff --git a/src/MissingValues/Info/NumberFormatter.cs b/src/MissingValues/Info/NumberFormatter.cs index 86a8e6a..a2eb2b4 100644 --- a/src/MissingValues/Info/NumberFormatter.cs +++ b/src/MissingValues/Info/NumberFormatter.cs @@ -108,14 +108,14 @@ private static bool TryFormatNumber( { digits = digitsArray = ArrayPool.Shared.Rent(NumberParser.QuadBufferLength); - number = new(digits[..NumberParser.QuadBufferLength]); + number = new(digits[..NumberParser.QuadBufferLength], true); Ryu.Format(in quad, ref number, out exceptional); } else if (value is Octo octo) { digits = digitsArray = ArrayPool.Shared.Rent(NumberParser.OctoBufferLength); - number = new(digits[..NumberParser.OctoBufferLength]); + number = new(digits[..NumberParser.OctoBufferLength], true); Ryu.Format(in octo, ref number, out exceptional); } else diff --git a/src/MissingValues/Info/NumberParser.cs b/src/MissingValues/Info/NumberParser.cs index 94398de..7591adc 100644 --- a/src/MissingValues/Info/NumberParser.cs +++ b/src/MissingValues/Info/NumberParser.cs @@ -137,6 +137,11 @@ internal void Throw(string input) } #region Integer + private const int IntBufferLength = 154 + 2; + private const NumberStyles SPECIAL = + NumberStyles.AllowTrailingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowExponent + | NumberStyles.AllowCurrencySymbol; + public static ParsingStatus ParseDecStringToUnsigned(ReadOnlySpan s, out T output) where T : struct, IFormattableInteger, IMinMaxValue, IUnsignedNumber where TChar : unmanaged, IUtfCharacter @@ -215,6 +220,20 @@ public static ParsingStatus TryParseToUnsigned(ReadOnlySpan s, return ParsingStatus.Underflow; } + if ((style & SPECIAL) != 0) + { + NumberInfo number = new NumberInfo(stackalloc byte[IntBufferLength], true); + NumberFormatInfo info = NumberFormatInfo.GetInstance(formatProvider); + if (!NumberInfo.TryParse(s, ref number, info, style) + || !NumberInfo.TryConvertToInteger(ref number, out output)) + { + output = default; + return ParsingStatus.Failed; + } + + return ParsingStatus.Success; + } + if (ContainsInvalidCharacter(s, style)) { output = default; @@ -280,6 +299,20 @@ public static ParsingStatus TryParseToSigned(ReadOnly raw = isNegative ? s[1..] : s; } + if ((style & SPECIAL) != 0) + { + NumberInfo number = new NumberInfo(stackalloc byte[IntBufferLength], true); + NumberFormatInfo info = NumberFormatInfo.GetInstance(formatProvider); + if (!NumberInfo.TryParse(s, ref number, info, style) + || !NumberInfo.TryConvertToInteger(ref number, out output)) + { + output = default; + return ParsingStatus.Failed; + } + + return ParsingStatus.Success; + } + if (ContainsInvalidCharacter(raw, style)) { output = default; @@ -351,7 +384,7 @@ public static unsafe bool TryParseFloat(ReadOnlySpan { byte[] buffer = ArrayPool.Shared.Rent(OctoBufferLength); - NumberInfo number = new NumberInfo(buffer); + NumberInfo number = new NumberInfo(buffer, true); NumberFormatInfo info = NumberFormatInfo.GetInstance(provider); diff --git a/src/MissingValues/Int256.Implementations.cs b/src/MissingValues/Int256.Implementations.cs index 3845f87..d66fcda 100644 --- a/src/MissingValues/Int256.Implementations.cs +++ b/src/MissingValues/Int256.Implementations.cs @@ -22,6 +22,7 @@ public partial struct Int256 : IBigInteger, IMinMaxValue, ISignedNumber, + IPowerFunctions, IFormattableSignedInteger { private static UInt128 _upperMin => new UInt128(0x8000_0000_0000_0000, 0x0000_0000_0000_0000); @@ -274,6 +275,10 @@ public static Int256 MinMagnitude(Int256 x, Int256 y) static Int256 INumberBase.MinMagnitudeNumber(Int256 x, Int256 y) => MinMagnitude(x, y); +#if NET9_0_OR_GREATER + static Int256 INumberBase.MultiplyAddEstimate(Int256 left, Int256 right, Int256 addend) => (left * right) + addend; +#endif + /// public static Int256 Max(Int256 x, Int256 y) => (x >= y) ? x : y; @@ -371,28 +376,18 @@ public static Int256 Parse(ReadOnlySpan utf8Text, IFormatProvider? provide } /// - public static Int256 PopCount(Int256 value) - { - return BitHelper.PopCount(in value); - } + public static Int256 PopCount(Int256 value) => BitHelper.PopCount(in value); + + static Int256 IPowerFunctions.Pow(Int256 x, Int256 y) => Pow(x, checked((int)y)); /// - public static Int256 RotateLeft(Int256 value, int rotateAmount) - { - return (value << rotateAmount) | (value >>> (256 - rotateAmount)); - } + public static Int256 RotateLeft(Int256 value, int rotateAmount) => (value << rotateAmount) | (value >>> (256 - rotateAmount)); /// - public static Int256 RotateRight(Int256 value, int rotateAmount) - { - return (value >>> rotateAmount) | (value << (256 - rotateAmount)); - } + public static Int256 RotateRight(Int256 value, int rotateAmount) => (value >>> rotateAmount) | (value << (256 - rotateAmount)); /// - public static Int256 TrailingZeroCount(Int256 value) - { - return BitHelper.TrailingZeroCount(in value); - } + public static Int256 TrailingZeroCount(Int256 value) => BitHelper.TrailingZeroCount(in value); /// public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Int256 result) @@ -661,6 +656,7 @@ private static bool TryConvertFromChecked(TOther value, out Int256 resul Int256 actual => actual, Int512 actual => (Int256)actual, nint actual => (Int256)actual, + BigInteger actual => (Int256)actual, _ => BitHelper.DefaultConvert(out converted) }; } @@ -695,8 +691,9 @@ private static bool TryConvertFromSaturating(TOther value, out Int256 re long actual => actual, Int128 actual => actual, Int256 actual => actual, - Int512 actual => (actual <= MinValue) ? MinValue : (actual >= MaxValue) ? MaxValue : (Int256)actual, + Int512 actual => (actual < MinValue) ? MinValue : (actual > MaxValue) ? MaxValue : (Int256)actual, nint actual => actual, + BigInteger actual => (actual < (BigInteger)MinValue) ? MinValue : (actual > (BigInteger)MaxValue) ? MaxValue : (Int256)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -730,6 +727,7 @@ private static bool TryConvertFromTruncating(TOther value, out Int256 re Int128 actual => actual, Int256 actual => actual, nint actual => actual, + BigInteger actual => (Int256)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -764,6 +762,7 @@ static bool INumberBase.TryConvertToChecked(Int256 value, out TO Int256 => (TOther)(object)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; } @@ -799,6 +798,7 @@ static bool INumberBase.TryConvertToSaturating(Int256 value, out Int256 => (TOther)(object)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)((value >= (Int256)nint.MaxValue) ? nint.MaxValue : (value <= (Int256)nint.MinValue) ? nint.MinValue : (nint)value), + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -832,6 +832,7 @@ static bool INumberBase.TryConvertToTruncating(Int256 value, out Int256 => (TOther)(object)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -1081,6 +1082,8 @@ static Int256 IFormattableInteger.GetHexValue(char value) throw new FormatException(); } + static int IFormattableInteger.UnsignedCompare(in Int256 value1, in Int256 value2) => unchecked(((UInt256)value1).CompareTo((UInt256)value2)); + /// public static Int256 operator +(in Int256 value) => value; diff --git a/src/MissingValues/Int256.cs b/src/MissingValues/Int256.cs index 9de1d74..69b9160 100644 --- a/src/MissingValues/Int256.cs +++ b/src/MissingValues/Int256.cs @@ -1,11 +1,13 @@ using MissingValues.Info; using MissingValues.Internals; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -133,6 +135,105 @@ public static Int256 BigMul(Int256 left, Int256 right, out Int256 low) return (Int256)(upper) - ((left >> 255) & right) - ((right >> 255) & left); } + /// + /// Raises a value to the power of a specified value. + /// + /// The number to raise to the power. + /// The exponent to raise by. + /// The result of raising to the power. + /// is negative. + /// + /// The result of raising to the power is less than or greater than . + /// + public static Int256 Pow(Int256 value, int exponent) + { + const int UIntCount = Size / sizeof(uint); + + ArgumentOutOfRangeException.ThrowIfNegative(exponent); + + if (exponent == 0) + { + return One; + } + if (exponent == 1) + { + return value; + } + + uint power = checked((uint)exponent); + int size; + uint[]? bitsArray = null; + scoped Span bits; + + if (value <= int.MaxValue && value >= int.MinValue) + { + int sign = (int)value; + if (sign == 1) + return value; + if (sign == -1) + return (exponent & 1) != 0 ? value : One; + if (sign == 0) + return value; + + if (power >= ((Size * 8) - 1)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + size = Calculator.PowBound(power, 1); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(unchecked((uint)sign), power, bits[..size]); + } + else + { + if (power >= ((Size * 8) - 1)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + int valueLength = (UIntCount - (BitHelper.LeadingZeroCount(in value) / 32)); + size = Calculator.PowBound(power, valueLength); + + Span valueSpan = stackalloc uint[UIntCount]; + valueSpan.Clear(); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)), value); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(valueSpan[..valueLength], power, bits[..size]); + } + + if (size > UIntCount) + { + Span overflow = bits[UIntCount..]; + + for (int i = 0; i < overflow.Length; i++) + { + if (overflow[i] != 0) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + } + } + + Int256 result = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(bits[..UIntCount]))); + + if (bitsArray is not null) + { + ArrayPool.Shared.Return(bitsArray); + } + + return result; + } + /// Parses a span of characters into a value. /// The span of characters to parse. /// The result of parsing . @@ -464,6 +565,26 @@ public static explicit operator checked nuint(in Int256 value) } return checked((nuint)value._p0); } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator BigInteger(in Int256 value) + { + if (~(value._p3 & value._p2 & value._p1) == 0) + { + return new BigInteger((long)value._p0); + } + if (value._p3 == 0 && value._p2 == 0 && value._p1 == 0) + { + return new BigInteger(value._p0); + } + + Span span = stackalloc byte[Size]; + value.WriteLittleEndianUnsafe(span); + return new BigInteger(span, value >= 0); + } // Floating /// /// Explicitly converts a value to a . @@ -641,6 +762,124 @@ public static explicit operator Int256(UInt128 value) return new Int256(0, 0, Unsafe.Add(ref Unsafe.As(ref value), 1), (ulong)value); } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int256(BigInteger value) + { + bool isUnsigned = BigInteger.IsPositive(value); + + Span span = stackalloc byte[value.GetByteCount()]; + value.TryWriteBytes(span, out int bytesWritten, isUnsigned); + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + Int256 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + Int256 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + result <<= ((Size - bytesWritten) * 8); + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - bytesWritten) * 8) - 1)); + } + + return result; + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int256(BigInteger value) + { + bool isUnsigned = BigInteger.IsPositive(value); + + Span span = stackalloc byte[isUnsigned ? Size : value.GetByteCount()]; + if (!value.TryWriteBytes(span, out int bytesWritten, isUnsigned)) + { + Thrower.IntegerOverflow(); + } + + // Propagate the most significant bit so we have `0` or `-1` + sbyte sign = (sbyte)(span[^1]); + sign >>= 31; + + // We need to also track if the input data is unsigned + isUnsigned |= (sign == 0); + + if (isUnsigned && sbyte.IsNegative(sign) && (bytesWritten >= Size)) + { + // When we are unsigned and the most significant bit is set, we are a large positive + // and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + + if (bytesWritten > Size) + { + if (span[Size..].IndexOfAnyExcept((byte)sign) >= 0) + { + // When we are unsigned and have any non-zero leading data or signed with any non-set leading + // data, we are a large positive/negative, respectively, and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + + if (isUnsigned == sbyte.IsNegative((sbyte)span[Size - 1])) + { + // When the most significant bit of the value being set/clear matches whether we are unsigned + // or signed then we are a large positive/negative and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + Int256 result = Zero; + + // We have between 1 and 63 bytes, so construct the relevant value directly + // since the data is in Little Endian format, we can just read the bytes and + // shift left by 8-bits for each subsequent part, then reverse endianness to + // ensure the order is correct. This is more efficient than iterating in reverse + // due to current JIT limitations + + for (int i = 0; i < bytesWritten; i++) + { + Int256 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + result <<= ((Size - bytesWritten) * 8); + result = BitHelper.ReverseEndianness(in result); + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - bytesWritten) * 8) - 1)); + } + + return result; + } + //Floating /// /// Explicitly converts a value to a . diff --git a/src/MissingValues/Int512.Implementations.cs b/src/MissingValues/Int512.Implementations.cs index bdf350c..1c39c29 100644 --- a/src/MissingValues/Int512.Implementations.cs +++ b/src/MissingValues/Int512.Implementations.cs @@ -19,6 +19,7 @@ public partial struct Int512 : IBigInteger, IMinMaxValue, ISignedNumber, + IPowerFunctions, IFormattableSignedInteger { private static UInt256 _upperMin => new UInt256(0x8000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000); @@ -371,6 +372,10 @@ public static Int512 MinMagnitude(Int512 x, Int512 y) static Int512 INumberBase.MinMagnitudeNumber(Int512 x, Int512 y) => MinMagnitude(x, y); +#if NET9_0_OR_GREATER + static Int512 INumberBase.MultiplyAddEstimate(Int512 left, Int512 right, Int512 addend) => (left * right) + addend; +#endif + /// public static Int512 Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) { @@ -442,6 +447,8 @@ public static Int512 Parse(ReadOnlySpan utf8Text, IFormatProvider? provide /// public static Int512 PopCount(Int512 value) => (Int512)(BitHelper.PopCount(in value)); + static Int512 IPowerFunctions.Pow(Int512 x, Int512 y) => Pow(x, checked((int)y)); + /// public static Int512 RotateLeft(Int512 value, int rotateAmount) => (value << rotateAmount) | (value >>> (512 - rotateAmount)); @@ -759,6 +766,7 @@ private static bool TryConvertFromChecked(TOther value, out Int512 resul Int256 actual => (Int512)actual, Int512 actual => actual, nint actual => (Int512)actual, + BigInteger actual => (Int512)actual, _ => BitHelper.DefaultConvert(out converted) }; } @@ -797,6 +805,7 @@ private static bool TryConvertFromSaturating(TOther value, out Int512 re Int256 actual => actual, Int512 actual => actual, nint actual => actual, + BigInteger actual => (actual < (BigInteger)MinValue) ? MinValue : (actual > (BigInteger)MaxValue) ? MaxValue : (Int512)actual, _ => BitHelper.DefaultConvert(out converted) }; @@ -834,6 +843,7 @@ private static bool TryConvertFromTruncating(TOther value, out Int512 re Int256 actual => actual, Int512 actual => actual, nint actual => actual, + BigInteger actual => (Int512)actual, _ => BitHelper.DefaultConvert(out converted) }; @@ -870,6 +880,7 @@ static bool INumberBase.TryConvertToChecked(Int512 value, out TO Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; } @@ -906,6 +917,7 @@ static bool INumberBase.TryConvertToSaturating(Int512 value, out Int256 => (TOther)(object)((value >= (Int512)Int256.MaxValue) ? Int256.MaxValue : (value <= (Int512)Int256.MinValue) ? Int128.MinValue : (Int256)value), Int512 => (TOther)(object)value, nint => (TOther)(object)((value >= (Int512)nint.MaxValue) ? nint.MaxValue : (value <= (Int512)nint.MinValue) ? nint.MinValue : (nint)value), + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -940,6 +952,7 @@ static bool INumberBase.TryConvertToTruncating(Int512 value, out Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -1035,6 +1048,7 @@ private void WriteLittleEndianUnsafe(Span destination) int IFormattableInteger.ToInt32() => (int)_p0; UInt512 IFormattableSignedInteger.ToUnsigned() => (UInt512)this; + static int IFormattableInteger.UnsignedCompare(in Int512 value1, in Int512 value2) => unchecked(((UInt512)value1).CompareTo((UInt512)value2)); /// public static Int512 operator +(in Int512 value) => value; diff --git a/src/MissingValues/Int512.cs b/src/MissingValues/Int512.cs index 4ee600d..7a93bc9 100644 --- a/src/MissingValues/Int512.cs +++ b/src/MissingValues/Int512.cs @@ -1,11 +1,13 @@ using MissingValues.Info; using MissingValues.Internals; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -182,6 +184,105 @@ public static Int512 BigMul(Int512 left, Int512 right, out Int512 low) return (Int512)(upper) - ((left >> 511) & right) - ((right >> 511) & left); } + /// + /// Raises a value to the power of a specified value. + /// + /// The number to raise to the power. + /// The exponent to raise by. + /// The result of raising to the power. + /// is negative. + /// + /// The result of raising to the power is less than or greater than . + /// + public static Int512 Pow(Int512 value, int exponent) + { + const int UIntCount = Size / sizeof(uint); + + ArgumentOutOfRangeException.ThrowIfNegative(exponent); + + if (exponent == 0) + { + return One; + } + if (exponent == 1) + { + return value; + } + + uint power = checked((uint)exponent); + int size; + uint[]? bitsArray = null; + scoped Span bits; + + if (value <= int.MaxValue && value >= int.MinValue) + { + int sign = (int)value; + if (sign == 1) + return value; + if (sign == -1) + return (exponent & 1) != 0 ? value : One; + if (sign == 0) + return value; + + if (power >= ((Size * 8) - 1)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + size = Calculator.PowBound(power, 1); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(unchecked((uint)sign), power, bits[..size]); + } + else + { + if (power >= ((Size * 8) - 1)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + int valueLength = (UIntCount - (BitHelper.LeadingZeroCount(in value) / 32)); + size = Calculator.PowBound(power, valueLength); + + Span valueSpan = stackalloc uint[UIntCount]; + valueSpan.Clear(); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)), value); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(valueSpan[..valueLength], power, bits[..size]); + } + + if (size > UIntCount) + { + Span overflow = bits[UIntCount..]; + + for (int i = 0; i < overflow.Length; i++) + { + if (overflow[i] != 0) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + } + } + + Int512 result = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(bits[..UIntCount]))); + + if (bitsArray is not null) + { + ArrayPool.Shared.Return(bitsArray); + } + + return result; + } + /// Parses a span of characters into a value. /// The span of characters to parse. /// The result of parsing . @@ -532,6 +633,26 @@ public static explicit operator checked nint(in Int512 value) } return checked((nint)value._p0); } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator BigInteger(in Int512 value) + { + if (~(value._p7 & value._p6 & value._p5 & value._p4 & value._p3 & value._p2 & value._p1) == 0) + { + return new BigInteger((long)value._p0); + } + if (value._p7 == 0 && value._p6 == 0 && value._p5 == 0 && value._p4 == 0 && value._p3 == 0 && value._p2 == 0 && value._p1 == 0) + { + return new BigInteger(value._p0); + } + + Span span = stackalloc byte[Size]; + value.WriteLittleEndianUnsafe(span); + return new BigInteger(span, value >= 0); + } // Floating /// /// Explicitly converts a value to a . @@ -715,6 +836,124 @@ public static implicit operator Int512(Int128 value) lowerShifted, lowerShifted, (ulong)Unsafe.Add(ref v, 1), (ulong)v ); } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int512(BigInteger value) + { + bool isUnsigned = BigInteger.IsPositive(value); + + Span span = stackalloc byte[value.GetByteCount()]; + value.TryWriteBytes(span, out int bytesWritten, isUnsigned); + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + Int512 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + Int512 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + result <<= ((Size - bytesWritten) * 8); + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - bytesWritten) * 8) - 1)); + } + + return result; + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int512(BigInteger value) + { + bool isUnsigned = BigInteger.IsPositive(value); + + Span span = stackalloc byte[isUnsigned ? Size : value.GetByteCount()]; + if (!value.TryWriteBytes(span, out int bytesWritten, isUnsigned)) + { + Thrower.IntegerOverflow(); + } + + // Propagate the most significant bit so we have `0` or `-1` + sbyte sign = (sbyte)(span[^1]); + sign >>= 31; + + // We need to also track if the input data is unsigned + isUnsigned |= (sign == 0); + + if (isUnsigned && sbyte.IsNegative(sign) && (bytesWritten >= Size)) + { + // When we are unsigned and the most significant bit is set, we are a large positive + // and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + + if (bytesWritten > Size) + { + if (span[Size..].IndexOfAnyExcept((byte)sign) >= 0) + { + // When we are unsigned and have any non-zero leading data or signed with any non-set leading + // data, we are a large positive/negative, respectively, and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + + if (isUnsigned == sbyte.IsNegative((sbyte)span[Size - 1])) + { + // When the most significant bit of the value being set/clear matches whether we are unsigned + // or signed then we are a large positive/negative and therefore definitely out of range + + Thrower.IntegerOverflow(); + } + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + Int512 result = Zero; + + // We have between 1 and 63 bytes, so construct the relevant value directly + // since the data is in Little Endian format, we can just read the bytes and + // shift left by 8-bits for each subsequent part, then reverse endianness to + // ensure the order is correct. This is more efficient than iterating in reverse + // due to current JIT limitations + + for (int i = 0; i < bytesWritten; i++) + { + Int512 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + result <<= ((Size - bytesWritten) * 8); + result = BitHelper.ReverseEndianness(in result); + + if (!isUnsigned) + { + result |= ((One << ((Size * 8) - 1)) >> (((Size - bytesWritten) * 8) - 1)); + } + + return result; + } //Floating /// /// Explicitly converts a value to a . diff --git a/src/MissingValues/Internals/BigNumber.cs b/src/MissingValues/Internals/BigNumber.cs index 9a634d6..91f9a5e 100644 --- a/src/MissingValues/Internals/BigNumber.cs +++ b/src/MissingValues/Internals/BigNumber.cs @@ -172,7 +172,7 @@ public static uint CountSignificantBits(UInt128 value) public static uint CountSignificantBits(UInt256 value) { - return 256 - (uint)UInt256.LeadingZeroCount(value); + return 256 - (uint)BitHelper.LeadingZeroCount(in value); } public static uint CountSignificantBits(T value) diff --git a/src/MissingValues/Internals/BitHelper.Ieee.cs b/src/MissingValues/Internals/BitHelper.Ieee.cs index 9077e0b..33a6f02 100644 --- a/src/MissingValues/Internals/BitHelper.Ieee.cs +++ b/src/MissingValues/Internals/BitHelper.Ieee.cs @@ -1,6 +1,7 @@ using MissingValues.Internals; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; @@ -11,6 +12,117 @@ namespace MissingValues { internal static partial class BitHelper { + public static Quad GetQuadFromParts(int sign, int exp, UInt128 man) + { + const int Bias = Quad.ExponentBias + Quad.BiasedExponentShift; + UInt128 bits; + + if (man == 0) + { + bits = 0; + } + else + { + // Normalize so that 0x0010 0000 0000 0000 is the highest bit set. + int cbitShift = ((int)UInt128.LeadingZeroCount(man)) - Quad.BiasedExponentLength; + if (cbitShift < 0) + man >>= -cbitShift; + else + man <<= cbitShift; + exp -= cbitShift; + + // Move the point to just behind the leading 1: 0x001.0 0000 0000 0000 + // (112 bits) and skew the exponent (by 0x3FF == 1023). + exp += Bias; + + if (exp >= Quad.MaxBiasedExponent) + { + // Infinity. + bits = Quad.PositiveInfinityBits; + } + else if (exp <= 0) + { + // Denormalized. + exp--; + if (exp < -Quad.BiasedExponentShift) + { + // Underflow to zero. + bits = 0; + } + else + { + bits = man >> -exp; + Debug.Assert(bits != 0); + } + } + else + { + // Mask off the implicit high bit. + bits = (man & Quad.TrailingSignificandMask) | ((UInt128)exp << Quad.BiasedExponentShift); + } + } + + if (sign < 0) + bits |= Quad.SignMask; + + return Quad.UInt128BitsToQuad(bits); + } + public static Octo GetOctoFromParts(int sign, int exp, UInt256 man) + { + const int Bias = Octo.ExponentBias + Octo.BiasedExponentShift; + UInt256 bits; + + if (man == 0) + { + bits = 0; + } + else + { + // Normalize so that 0x0010 0000 0000 0000 is the highest bit set. + int cbitShift = (LeadingZeroCount(in man)) - Octo.BiasedExponentLength; + if (cbitShift < 0) + man >>= -cbitShift; + else + man <<= cbitShift; + exp -= cbitShift; + + // Move the point to just behind the leading 1: 0x001.0 0000 0000 0000 + // (112 bits) and skew the exponent (by 0x3FF == 1023). + exp += Bias; + + if (exp >= Octo.MaxBiasedExponent) + { + // Infinity. + bits = Octo.PositiveInfinityBits; + } + else if (exp <= 0) + { + // Denormalized. + exp--; + if (exp < -Octo.BiasedExponentShift) + { + // Underflow to zero. + bits = 0; + } + else + { + bits = man >> -exp; + Debug.Assert(bits != 0); + } + } + else + { + // Mask off the implicit high bit. + bits = (man & Octo.TrailingSignificandMask) | ((UInt256)exp << Octo.BiasedExponentShift); + } + } + + if (sign < 0) + bits |= Octo.SignMask; + + return Octo.UInt256BitsToOcto(bits); + } + public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulong man, out bool fFinite) { ulong bits = BitConverter.DoubleToUInt64Bits(dbl); @@ -38,6 +150,62 @@ public static void GetDoubleParts(double dbl, out int sign, out int exp, out ulo exp -= 1075; } } + public static void GetQuadParts(Quad dbl, out int sign, out int exp, out UInt128 man, out bool fFinite) + { + const int Bias = Quad.ExponentBias + Quad.BiasedExponentShift; + UInt128 bits = Quad.QuadToUInt128Bits(dbl); + + sign = 1 - ((int)(bits >> 126) & 2); + man = bits & Quad.TrailingSignificandMask; + exp = (int)(bits >> Quad.BiasedExponentShift) & Quad.MaxBiasedExponent; + if (exp == 0) + { + // Denormalized number. + fFinite = true; + if (man != UInt128.Zero) + exp = -(Bias - 1); + } + else if (exp == Quad.MaxBiasedExponent) + { + // NaN or Infinite. + fFinite = false; + exp = int.MaxValue; + } + else + { + fFinite = true; + man |= Quad.SignificandSignMask; + exp -= Bias; + } + } + public static void GetOctoParts(in Octo dbl, out int sign, out int exp, out UInt256 man, out bool fFinite) + { + const int Bias = Octo.ExponentBias + Octo.BiasedExponentShift; + UInt256 bits = Octo.OctoToUInt256Bits(dbl); + + sign = 1 - ((int)(bits >> 254) & 2); + man = bits & Octo.TrailingSignificandMask; + exp = (int)(bits >> Octo.BiasedExponentShift) & Octo.MaxBiasedExponent; + if (exp == 0) + { + // Denormalized number. + fFinite = true; + if (man != UInt256.Zero) + exp = -(Bias - 1); + } + else if (exp == Octo.MaxBiasedExponent) + { + // NaN or Infinite. + fFinite = false; + exp = int.MaxValue; + } + else + { + fFinite = true; + man |= Octo.SignificandSignMask; + exp -= Bias; + } + } private static ReadOnlySpan ApproxRecip_1k0s => [ diff --git a/src/MissingValues/Internals/Calculator.cs b/src/MissingValues/Internals/Calculator.cs index c72f4c8..75e153b 100644 --- a/src/MissingValues/Internals/Calculator.cs +++ b/src/MissingValues/Internals/Calculator.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; @@ -7,11 +8,17 @@ namespace MissingValues.Internals; internal static class Calculator { + public const int StackAllocThreshold = 64; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static UInt128 BigMul(ulong a, ulong b) { +#if NET9_0_OR_GREATER + return Math.BigMul(a, b); +#else ulong high = Math.BigMul(a, b, out ulong low); return new UInt128(high, low); +#endif } /// /// Produces the full product of two unsigned 128-bit numbers. @@ -50,6 +57,50 @@ public static (ulong Quotient, uint Remainder) DivRemByUInt32(ulong left, uint r return (quotient, (uint)left - ((uint)quotient * right)); } + public static void Square(ReadOnlySpan value, Span bits) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs + + Debug.Assert(bits.Length == value.Length + value.Length); + + // Executes different algorithms for computing z = a * a + // based on the actual length of a. If a is "small" enough + // we stick to the classic "grammar-school" method; for the + // rest we switch to implementations with less complexity + // albeit more overhead (which needs to pay off!). + + // Switching to managed references helps eliminating + // index bounds check... + ref uint resultPtr = ref MemoryMarshal.GetReference(bits); + + // Squares the bits using the "grammar-school" method. + // Envisioning the "rhombus" of a pen-and-paper calculation + // we see that computing z_i+j += a_j * a_i can be optimized + // since a_j * a_i = a_i * a_j (we're squaring after all!). + // Thus, we directly get z_i+j += 2 * a_j * a_i + c. + + // ATTENTION: an ordinary multiplication is safe, because + // z_i+j + a_j * a_i + c <= 2(2^32 - 1) + (2^32 - 1)^2 = + // = 2^64 - 1 (which perfectly matches with ulong!). But + // here we would need an UInt65... Hence, we split these + // operation and do some extra shifts. + for (int i = 0; i < value.Length; i++) + { + ulong carry = 0UL; + uint v = value[i]; + for (int j = 0; j < i; j++) + { + ulong digit1 = Unsafe.Add(ref resultPtr, i + j) + carry; + ulong digit2 = (ulong)value[j] * v; + Unsafe.Add(ref resultPtr, i + j) = unchecked((uint)(digit1 + (digit2 << 1))); + carry = (digit2 + (digit1 >> 1)) >> 31; + } + ulong digits = (ulong)v * v + carry; + Unsafe.Add(ref resultPtr, i + i) = unchecked((uint)digits); + Unsafe.Add(ref resultPtr, i + i + 1) = (uint)(digits >> 32); + } + } + public static UInt256 Multiply(in UInt256 left, uint right, out uint carry) { // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.SquMul.cs @@ -421,4 +472,126 @@ public static void Remainder(ReadOnlySpan left, ReadOnlySpan right, Divide(remainder, right, default); } + + public static void Pow(uint value, uint power, Span bits) + { + Pow(value != 0 ? new ReadOnlySpan(in value) : default, power, bits); + } + public static void Pow(ReadOnlySpan value, uint power, Span bits) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs + + Debug.Assert(bits.Length == PowBound(power, value.Length)); + + Span temp = stackalloc uint[bits.Length]; + temp.Clear(); + + Span valueCopy = stackalloc uint[bits.Length]; + value.CopyTo(valueCopy); + valueCopy[value.Length..].Clear(); + + Span result = PowCore(valueCopy, value.Length, temp, power, bits); + result.CopyTo(bits); + bits[result.Length..].Clear(); + } + + private static Span PowCore(Span value, int valueLength, Span temp, uint power, Span result) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs + Debug.Assert(value.Length >= valueLength); + Debug.Assert(temp.Length == result.Length); + Debug.Assert(value.Length == temp.Length); + + result[0] = 1; + int resultLength = 1; + + // The basic pow algorithm using square-and-multiply. + while (power != 0) + { + if ((power & 1) == 1) + resultLength = MultiplySelf(ref result, resultLength, value[..valueLength], ref temp); + if (power != 1) + valueLength = SquareSelf(ref value, valueLength, ref temp); + power >>= 1; + } + + return result[..resultLength]; + } + + private static int MultiplySelf(ref Span left, int leftLength, ReadOnlySpan right, ref Span temp) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs + Debug.Assert(leftLength <= left.Length); + + int resultLength = leftLength + right.Length; + + if (leftLength >= right.Length) + { + Multiply(left[..leftLength], right, temp[..resultLength]); + } + else + { + Multiply(right, left[..leftLength], temp[..resultLength]); + } + + left.Clear(); + //switch buffers + Span t = left; + left = temp; + temp = t; + return ActualLength(left[..resultLength]); + } + + private static int SquareSelf(ref Span value, int valueLength, ref Span temp) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs + Debug.Assert(valueLength <= value.Length); + Debug.Assert(temp.Length >= valueLength + valueLength); + + int resultLength = valueLength + valueLength; + + Square(value[..valueLength], temp[..resultLength]); + + value.Clear(); + //switch buffers + Span t = value; + value = temp; + temp = t; + return ActualLength(value[..resultLength]); + } + + public static int PowBound(uint power, int valueLength) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs + // The basic pow algorithm, but instead of squaring + // and multiplying we just sum up the lengths. + + int resultLength = 1; + while (power != 0) + { + checked + { + if ((power & 1) == 1) + resultLength += valueLength; + if (power != 1) + valueLength += valueLength; + } + power >>= 1; + } + + return resultLength; + } + + public static int ActualLength(ReadOnlySpan value) + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs + // Since we're reusing memory here, the actual length + // of a given value may be less then the array's length + + int length = value.Length; + + while (length > 0 && value[length - 1] == 0) + --length; + return length; + } } \ No newline at end of file diff --git a/src/MissingValues/Internals/NumberInfo.cs b/src/MissingValues/Internals/NumberInfo.cs index d009e54..92ab8d6 100644 --- a/src/MissingValues/Internals/NumberInfo.cs +++ b/src/MissingValues/Internals/NumberInfo.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace MissingValues.Internals { @@ -64,6 +65,7 @@ internal ref struct NumberInfo public int DigitsCount; public int Scale; public bool IsNegative; + public bool IsFloating; public bool HasNonZeroTail; public Span Digits; @@ -72,10 +74,89 @@ public NumberInfo(Span digits) DigitsCount = 0; Scale = 0; IsNegative = false; + IsFloating = false; HasNonZeroTail = false; Digits = digits; Digits[0] = (byte)'\0'; } + public NumberInfo(Span digits, bool isFloating) + { + DigitsCount = 0; + Scale = 0; + IsNegative = false; + IsFloating = isFloating; + HasNonZeroTail = false; + Digits = digits; + Digits[0] = (byte)'\0'; + } + + public static bool TryConvertToInteger(ref NumberInfo number, out TInteger value) + where TInteger : struct, IFormattableInteger, IMinMaxValue + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs + + int i = number.Scale; + + if ((i > TInteger.MaxDecimalDigits) || (i < number.DigitsCount) || (TInteger.IsUnsignedInteger && number.IsNegative) || number.HasNonZeroTail) + { + value = default; + return false; + } + + ref byte p = ref number.GetDigitsReference(); + + Debug.Assert(!Unsafe.IsNullRef(ref p)); + TInteger n = TInteger.Zero; + TInteger ten = TInteger.Ten; + TInteger maxValueDiv10 = TInteger.MaxValue / ten; + + while (--i >= 0) + { + if (TInteger.UnsignedCompare(in n, in maxValueDiv10) > 0) + { + value = default; + return false; + } + + n *= ten; + + if (p != '\0') + { + TInteger newN = n + TInteger.GetDecimalValue((char)p); + p = ref Unsafe.Add(ref p, 1); + + if (TInteger.IsUnsignedInteger && (newN < n)) + { + value = default; + return false; + } + + n = newN; + } + } + + if (!TInteger.IsUnsignedInteger) + { + if (number.IsNegative) + { + n = -n; + + if (n > TInteger.Zero) + { + value = default; + return false; + } + } + else if (n < TInteger.Zero) + { + value = default; + return false; + } + } + + value = n; + return true; + } public static TFloat ConvertToFloat(ref NumberInfo number) where TFloat : struct, IBinaryFloatingPointInfo @@ -105,83 +186,136 @@ public override string ToString() return Encoding.UTF8.GetString(Digits[..DigitsCount]); } - public static unsafe bool TryParse(ReadOnlySpan s, ref NumberInfo info, NumberFormatInfo formatInfo, NumberStyles styles = NumberStyles.Float) + public static unsafe bool TryParse(ReadOnlySpan s, ref NumberInfo info, NumberFormatInfo formatInfo, NumberStyles styles) where TChar : unmanaged, IUtfCharacter { + fixed(TChar* stringPointer = &MemoryMarshal.GetReference(s)) + { + TChar* p = stringPointer; + if(!TryParse(ref p, p + s.Length, ref info, formatInfo, styles) + || ((int)(p - stringPointer) < s.Length && !TrailingZeros(s, (int)(p - stringPointer)))) + { + return false; + } + + return true; + } + } + public static unsafe bool TryParse(scoped ref TChar* str, TChar* strEnd, ref NumberInfo number, NumberFormatInfo info, NumberStyles styles) + where TChar : unmanaged, IUtfCharacter + { + // Based on: https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Number.NumberBuffer.cs + + Debug.Assert(str != null); + Debug.Assert(strEnd != null); + Debug.Assert(str <= strEnd); + Debug.Assert((styles & (NumberStyles.AllowHexSpecifier | NumberStyles.AllowBinarySpecifier)) == 0); + const int StateSign = 0x0001; const int StateParens = 0x0002; const int StateDigits = 0x0004; const int StateNonZero = 0x0008; const int StateDecimal = 0x0010; + const int StateCurrency = 0x0020; + Debug.Assert(number.DigitsCount == 0); + Debug.Assert(number.Scale == 0); + Debug.Assert(!number.IsNegative); + Debug.Assert(!number.HasNonZeroTail); - Span decSep = stackalloc TChar[TChar.GetLength(formatInfo.NumberDecimalSeparator)]; - Span groupSep = stackalloc TChar[TChar.GetLength(formatInfo.NumberGroupSeparator)]; - Span positiveSign = stackalloc TChar[TChar.GetLength(formatInfo.PositiveSign)]; - Span negativeSign = stackalloc TChar[TChar.GetLength(formatInfo.NegativeSign)]; + scoped Span decSep; + scoped Span groupSep; + scoped Span currSymbol; + Span positiveSign = stackalloc TChar[TChar.GetLength(info.PositiveSign)]; + Span negativeSign = stackalloc TChar[TChar.GetLength(info.NegativeSign)]; - TChar.Copy(formatInfo.NumberDecimalSeparator, decSep); - TChar.Copy(formatInfo.NumberGroupSeparator, groupSep); - TChar.Copy(formatInfo.PositiveSign, positiveSign); - TChar.Copy(formatInfo.NegativeSign, negativeSign); + bool parsingCurrency; - State state = 0; - - if ((styles & NumberStyles.AllowLeadingWhite) != 0) + if ((styles & NumberStyles.AllowCurrencySymbol) != 0) { - s = s.TrimStart(TChar.WhiteSpaceCharacter); + decSep = stackalloc TChar[TChar.GetLength(info.CurrencyDecimalSeparator)]; + groupSep = stackalloc TChar[TChar.GetLength(info.CurrencyGroupSeparator)]; + currSymbol = stackalloc TChar[TChar.GetLength(info.CurrencySymbol)]; + + TChar.Copy(info.CurrencyDecimalSeparator, decSep); + TChar.Copy(info.CurrencyGroupSeparator, groupSep); + TChar.Copy(info.CurrencySymbol, currSymbol); + + parsingCurrency = true; } - if ((styles & NumberStyles.AllowLeadingWhite) != 0) + else { - s = s.TrimEnd(TChar.WhiteSpaceCharacter); - } + decSep = stackalloc TChar[TChar.GetLength(info.NumberDecimalSeparator)]; + groupSep = stackalloc TChar[TChar.GetLength(info.NumberGroupSeparator)]; + currSymbol = []; - int totalLength = s.Length; - int curIndex = 0; - ref TChar ptr = ref MemoryMarshal.GetReference(s); - TChar ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + TChar.Copy(info.NumberDecimalSeparator, decSep); + TChar.Copy(info.NumberGroupSeparator, groupSep); - int digCount = 0; - int digEnd = 0; - int maxDigCount = info.Digits.Length - 1; - int numberOfTrailingZeros = 0; + parsingCurrency = false; + } + TChar.Copy(info.PositiveSign, positiveSign); + TChar.Copy(info.NegativeSign, negativeSign); - if ((styles & NumberStyles.AllowLeadingSign) != 0) - { - if (s.IndexOf(positiveSign) == 0) - { - info.IsNegative = false; - state.Add(StateSign); + State state = 0; + TChar* p = str; + uint ch = (p < strEnd) ? (uint)(*p) : '\0'; + TChar* next; - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; - } - else if (s.IndexOf(negativeSign) == 0) + while (true) + { + // Eat whitespace unless we've found a sign which isn't followed by a currency symbol. + // "-Kr 1231.47" is legal but "- 1231.47" is not. + if (!IsWhite(ch) || (styles & NumberStyles.AllowLeadingWhite) == 0 || (state.Contains(StateSign) && !state.Contains(StateCurrency) && info.NumberNegativePattern != 2)) { - info.IsNegative = true; - state.Add(StateSign); - - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + if (((styles & NumberStyles.AllowLeadingSign) != 0) && !state.Contains(StateSign) && ((next = MatchChars(p, strEnd, positiveSign)) != null || ((next = MatchChars(p, strEnd, negativeSign)) != null && (number.IsNegative = true)))) + { + state.Add(StateSign); + p = next - 1; + } + else if (ch == '(' && ((styles & NumberStyles.AllowParentheses) != 0) && (!state.Contains(StateSign))) + { + state.Add(StateSign | StateParens); + number.IsNegative = true; + } + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) + { + state.Add(StateCurrency); + currSymbol = []; + // We already found the currency symbol. There should not be more currency symbols. Set + // currSymbol to NULL so that we won't search it again in the later code path. + p = next - 1; + } + else + { + break; + } } + ch = ++p < strEnd ? (uint)(*p) : '\0'; } + int digCount = 0; + int digEnd = 0; + int maxDigCount = number.Digits.Length - 1; + int numberOfTrailingZeros = 0; + while (true) { - if (TChar.IsDigit(ch)) + if (IsDigit(ch)) { state.Add(StateDigits); - if (ch != (TChar)'0' || state.Contains(StateNonZero)) + + if (ch != '0' || state.Contains(StateNonZero)) { if (digCount < maxDigCount) { - info.Digits[digCount] = (byte)ch; - digEnd = digCount + 1; - + number.Digits[digCount] = (byte)ch; + if ((ch != '0') || (number.IsFloating)) + { + digEnd = digCount + 1; + } } - else if (ch != (TChar)'0') + else if (ch != '0') { // For decimal and binary floating-point numbers, we only // need to store digits up to maxDigCount. However, we still @@ -190,19 +324,18 @@ public static unsafe bool TryParse(ReadOnlySpan s, ref NumberInfo // for an input that falls evenly between two representable // results. - info.HasNonZeroTail = true; + number.HasNonZeroTail = true; } if (!state.Contains(StateDecimal)) { - info.Scale++; + number.Scale++; } if (digCount < maxDigCount) { - // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, - // so we keep a count of the number of trailing zeros and update digCount later - if (ch == (TChar)'0') + // Handle a case like "53.0". We need to ignore trailing zeros in the fractional part for floating point numbers, so we keep a count of the number of trailing zeros and update digCount later + if (ch == '0') { numberOfTrailingZeros++; } @@ -216,147 +349,133 @@ public static unsafe bool TryParse(ReadOnlySpan s, ref NumberInfo } else if (state.Contains(StateDecimal)) { - info.Scale--; + number.Scale--; } } - else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && (!state.Contains(StateDecimal)) && (TChar.Constains(s[curIndex..], decSep, StringComparison.OrdinalIgnoreCase))) + else if (((styles & NumberStyles.AllowDecimalPoint) != 0) && !state.Contains(StateDecimal) && ((next = MatchChars(p, strEnd, decSep)) != null || (parsingCurrency && !state.Contains(StateCurrency) && (next = MatchChars(p, strEnd, info.NumberDecimalSeparator)) != null))) { state.Add(StateDecimal); - int decSepIndex = s.IndexOf(decSep); - ptr = ref MemoryMarshal.GetReference(s[decSepIndex..]); - curIndex = decSepIndex; + p = next - 1; } - else if (((styles & NumberStyles.AllowThousands) != 0) && (state.Contains(StateDigits)) && (!state.Contains(StateDecimal)) && (TChar.Constains(s[curIndex..], groupSep, StringComparison.OrdinalIgnoreCase))) + else if (((styles & NumberStyles.AllowThousands) != 0) && state.Contains(StateDigits) && !state.Contains(StateDecimal) && ((next = MatchChars(p, strEnd, groupSep)) != null || (parsingCurrency && !state.Contains(StateCurrency) && (next = MatchChars(p, strEnd, info.NumberGroupSeparator)) != null))) { - int groupSepIndex = s[curIndex..].IndexOf(groupSep) + curIndex; - ptr = ref MemoryMarshal.GetReference(s[groupSepIndex..]); - curIndex = groupSepIndex; + p = next - 1; } else { break; } - - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + ch = ++p < strEnd ? (uint)(*p) : '\0'; } bool negExp = false; - info.DigitsCount = digEnd; - info.Digits[digEnd] = (byte)'\0'; + number.DigitsCount = digEnd; + number.Digits[digEnd] = (byte)'\0'; if (state.Contains(StateDigits)) { - if ((ch == (TChar)'E' || ch == (TChar)'e') && ((styles & NumberStyles.AllowExponent) != 0)) + if ((ch == 'E' || ch == 'e') && ((styles & NumberStyles.AllowExponent) != 0)) { - ref TChar temp = ref ptr; - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; - - if (s[curIndex..].IndexOf(positiveSign) == 0) + TChar* temp = p; + ch = ++p < strEnd ? (uint)(*p) : '\0'; + if ((next = MatchChars(p, strEnd, positiveSign)) != null) { - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + ch = (p = next) < strEnd ? (uint)(*p) : '\0'; } - else if (s[curIndex..].IndexOf(negativeSign) == 0) + else if ((next = MatchChars(p, strEnd, negativeSign)) != null) { - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + ch = (p = next) < strEnd ? (uint)(*p) : '\0'; negExp = true; } - - if (TChar.IsDigit(ch)) + if (IsDigit(ch)) { int exp = 0; do { - exp = (exp * 10) + ((char)ch - '0'); - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; - if (exp > 5000) + // Check if we are about to overflow past our limit of 9 digits + if (exp >= 100_000_000) { - exp = 9999; - while (TChar.IsDigit(ch)) + // Set exp to Int.MaxValue to signify the requested exponent is too large. This will lead to an OverflowException later. + exp = int.MaxValue; + number.Scale = 0; + + // Finish parsing the number, a FormatException could still occur later on. + while (IsDigit(ch)) { - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + ch = ++p < strEnd ? (uint)(*p) : '\0'; } + break; } - } while (TChar.IsDigit(ch)); + exp = (exp * 10) + (int)(ch - '0'); + ch = ++p < strEnd ? (uint)(*p) : '\0'; + } while (IsDigit(ch)); if (negExp) { exp = -exp; } - info.Scale += exp; + number.Scale += exp; } else { - ptr = ref temp; + p = temp; + ch = p < strEnd ? (uint)(*p) : '\0'; } } - if (!info.HasNonZeroTail) + if (number.IsFloating && !number.HasNonZeroTail) { - int numberOfFractionalDigits = digEnd - info.Scale; + // Adjust the number buffer for trailing zeros + int numberOfFractionalDigits = digEnd - number.Scale; if (numberOfFractionalDigits > 0) { numberOfTrailingZeros = Math.Min(numberOfTrailingZeros, numberOfFractionalDigits); Debug.Assert(numberOfTrailingZeros >= 0); - info.DigitsCount = digEnd - numberOfTrailingZeros; - info.Digits[info.DigitsCount] = (byte)'\0'; + number.DigitsCount = digEnd - numberOfTrailingZeros; + number.Digits[number.DigitsCount] = (byte)'\0'; } } while (true) { - if (!TChar.IsWhiteSpace(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) + if (!IsWhite(ch) || (styles & NumberStyles.AllowTrailingWhite) == 0) { - int tempIndex = 0; - if ((styles & NumberStyles.AllowTrailingSign) != 0 && (!state.Contains(StateSign)) && ((tempIndex = s[curIndex..].IndexOf(positiveSign)) >= 0 || (tempIndex = s[curIndex..].IndexOf(negativeSign)) >= 0) && (info.IsNegative = true)) + if ((styles & NumberStyles.AllowTrailingSign) != 0 && !state.Contains(StateSign) && ((next = MatchChars(p, strEnd, positiveSign)) != null || (((next = MatchChars(p, strEnd, negativeSign)) != null) && (number.IsNegative = true)))) { state.Add(StateSign); - tempIndex += curIndex; - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, tempIndex - curIndex) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + p = next - 1; } - else if (ch == (TChar)')' && state.Contains(StateParens)) + else if (ch == ')' && state.Contains(StateParens)) { state.Remove(StateParens); } + else if (!currSymbol.IsEmpty && (next = MatchChars(p, strEnd, currSymbol)) != null) + { + currSymbol = []; + p = next - 1; + } else { break; } } - curIndex++; - ptr = ref curIndex < totalLength ? ref Unsafe.Add(ref ptr, 1) : ref Unsafe.NullRef(); - ch = curIndex < totalLength ? ptr : TChar.NullCharacter; + ch = ++p < strEnd ? (uint)(*p) : '\0'; } - if (!state.Contains(StateParens)) { if (!state.Contains(StateNonZero)) { - info.Scale = 0; - } - - // Check if we got any value after parsing, if there was any invalid character - // this will return false - if (s[curIndex..].IndexOfAnyExcept(TChar.NullCharacter) >= 0) - { - return false; + number.Scale = 0; + + if ((!number.IsFloating) && !state.Contains(StateDecimal)) + { + number.IsNegative = false; + } } - + str = p; return true; } } + str = p; return false; } @@ -879,5 +998,131 @@ internal ref byte GetDigitsReference() { return ref MemoryMarshal.GetReference(Digits); } + + private static bool IsWhite(uint ch) => (ch == 0x20) || ((ch - 0x09) <= (0x0D - 0x09)); + + private static bool IsDigit(uint ch) => (ch - '0') <= 9; + + private static bool IsSpaceReplacingChar(uint c) => (c == '\u00a0') || (c == '\u202f'); + + [MethodImpl(MethodImplOptions.NoInlining)] // rare slow path that shouldn't impact perf of the main use case + private static bool TrailingZeros(ReadOnlySpan value, int index) + where TChar : unmanaged, IUtfCharacter + { + // For compatibility, we need to allow trailing zeros at the end of a number string + return !value.Slice(index).ContainsAnyExcept((TChar)('\0')); + } + + private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) + where TChar : unmanaged, IUtfCharacter + { + Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd)); + + fixed (TChar* stringPointer = &MemoryMarshal.GetReference(value)) + { + TChar* str = stringPointer; + + if ((uint)(*str) != '\0') + { + // We only hurt the failure case + // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a + // space character we use 0x20 space character instead to mean the same. + while (true) + { + uint cp = (p < pEnd) ? (uint)(*p) : '\0'; + uint val = (uint)(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) + { + break; + } + + p++; + str++; + + if ((uint)(*str) == '\0') + { + return p; + } + } + } + } + + return null; + } + private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) + where TChar : unmanaged, IUtfCharacter + { + Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd)); + + fixed (char* stringPointer = &MemoryMarshal.GetReference(value)) + { + char* str = stringPointer; + + if ((uint)(*str) != '\0') + { + // We only hurt the failure case + // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a + // space character we use 0x20 space character instead to mean the same. + while (true) + { + uint cp = (p < pEnd) ? (uint)(*p) : '\0'; + uint val = (uint)(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) + { + break; + } + + p++; + str++; + + if ((uint)(*str) == '\0') + { + return p; + } + } + } + } + + return null; + } + private static unsafe TChar* MatchChars(TChar* p, TChar* pEnd, ReadOnlySpan value) + where TChar : unmanaged, IUtfCharacter + { + Debug.Assert((p != null) && (pEnd != null) && (p <= pEnd)); + + fixed (byte* stringPointer = &MemoryMarshal.GetReference(value)) + { + byte* str = stringPointer; + + if ((uint)(*str) != '\0') + { + // We only hurt the failure case + // This fix is for French or Kazakh cultures. Since a user cannot type 0xA0 or 0x202F as a + // space character we use 0x20 space character instead to mean the same. + while (true) + { + uint cp = (p < pEnd) ? (uint)(*p) : '\0'; + uint val = (uint)(*str); + + if ((cp != val) && !(IsSpaceReplacingChar(val) && (cp == '\u0020'))) + { + break; + } + + p++; + str++; + + if ((uint)(*str) == '\0') + { + return p; + } + } + } + } + + return null; + } } } diff --git a/src/MissingValues/Internals/Thrower.cs b/src/MissingValues/Internals/Thrower.cs index c7b085c..494aa68 100644 --- a/src/MissingValues/Internals/Thrower.cs +++ b/src/MissingValues/Internals/Thrower.cs @@ -31,7 +31,7 @@ internal enum ParsingErrorType : byte } internal enum ArithmethicOperation : byte { - Addition, Subtraction, Multiplication, Division + Addition, Subtraction, Multiplication, Division, Exponentiation } [DoesNotReturn] diff --git a/src/MissingValues/MissingValues.csproj b/src/MissingValues/MissingValues.csproj index 559f5e7..cb7b314 100644 --- a/src/MissingValues/MissingValues.csproj +++ b/src/MissingValues/MissingValues.csproj @@ -1,7 +1,7 @@  - net8.0 + net8.0;net9.0 enable true enable @@ -17,7 +17,7 @@ https://github.com/crookseta/missing-values/releases MIT false - 2.0.0 + 2.1.0 True snupkg diff --git a/src/MissingValues/Octo.Implementations.cs b/src/MissingValues/Octo.Implementations.cs index 8ca4b97..4aaa76f 100644 --- a/src/MissingValues/Octo.Implementations.cs +++ b/src/MissingValues/Octo.Implementations.cs @@ -599,7 +599,7 @@ public static bool IsPow2(Octo value) if (biasedExponent == MinBiasedExponent) { // Subnormal values have 1 bit set when they're powers of 2 - return UInt256.PopCount(trailingSignificand) == UInt256.One; + return BitHelper.PopCount(in trailingSignificand) == 1; } else if (biasedExponent == MaxBiasedExponent) { @@ -859,6 +859,10 @@ public static Octo MinMagnitudeNumber(Octo x, Octo y) return y; } +#if NET9_0_OR_GREATER + static Octo INumberBase.MultiplyAddEstimate(Octo left, Octo right, Octo addend) => (left * right) + addend; +#endif + /// public static Octo Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) { @@ -1509,6 +1513,7 @@ private static bool TryConvertFrom(TOther value, out Octo result) Int128 actual => (Octo)actual, Int256 actual => (Octo)actual, Int512 actual => (Octo)actual, + BigInteger actual => (Octo)actual, _ => BitHelper.DefaultConvert(out converted) }; @@ -1544,6 +1549,7 @@ static bool INumberBase.TryConvertToChecked(Octo value, out TOther Int128 => (TOther)(object)(Int128)value, Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, + BigInteger => (TOther)(object)(BigInteger)value, nint => (TOther)(object)(nint)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -1552,15 +1558,9 @@ static bool INumberBase.TryConvertToChecked(Octo value, out TOther return converted; } - static bool INumberBase.TryConvertToSaturating(Octo value, out TOther result) - { - return TryConvertTo(value, out result); - } + static bool INumberBase.TryConvertToSaturating(Octo value, out TOther result) => TryConvertTo(value, out result); - static bool INumberBase.TryConvertToTruncating(Octo value, out TOther result) - { - return TryConvertTo(value, out result); - } + static bool INumberBase.TryConvertToTruncating(Octo value, out TOther result) => TryConvertTo(value, out result); private static bool TryConvertTo(Octo value, out TOther result) { @@ -1595,6 +1595,7 @@ private static bool TryConvertTo(Octo value, out TOther result) Int512 => (TOther)(object)((value >= new Octo(0x401F_E000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int512.MaxValue : (value <= new Octo(0xC01F_E000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int512.MinValue : (Int512)value), nint => (TOther)(object)((value >= nint.MaxValue) ? nint.MaxValue : (value <= nint.MinValue) ? nint.MinValue : (nint)value), + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; diff --git a/src/MissingValues/Octo.cs b/src/MissingValues/Octo.cs index b6a0cfa..aa9d7e9 100644 --- a/src/MissingValues/Octo.cs +++ b/src/MissingValues/Octo.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.Json.Serialization; @@ -1347,6 +1348,64 @@ public static explicit operator checked Int512(in Octo value) return Int512.Zero; } } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is not finite. + public static explicit operator BigInteger(in Octo value) + { + const int kcbitUInt128 = 128; + const int kcbitUInt256 = 256; + + if (!IsFinite(value)) + { + Thrower.IntegerOverflow(); + } + + BitHelper.GetOctoParts(in value, out int sign, out int exp, out var man, out _); + + if (man == UInt128.Zero) + { + return BigInteger.Zero; + } + + if (exp <= 0) + { + if (exp <= -kcbitUInt256) + { + return BigInteger.Zero; + } + return (BigInteger)(sign < 0 ? -(man >> -exp) : (man >> -exp)); + } + else if (exp <= BiasedExponentLength) + { + return (BigInteger)(sign < 0 ? -(man << exp) : (man << exp)); + } + else + { + // Overflow into at least 3 uints. + // Move the leading 1 to the high bit. + man <<= BiasedExponentLength; + exp -= BiasedExponentLength; + + // Compute cu and cbit so that exp == 32 * cu - cbit and 0 <= cbit < 32. + int cu = (exp - 1) / kcbitUInt128 + 1; + int cbit = cu * kcbitUInt128 - exp; + Debug.Assert(0 <= cbit && cbit < kcbitUInt128); + Debug.Assert(cu >= 1); + + // Populate the uints. + Span bits = stackalloc UInt128[cu + 2]; + bits[cu + 1] = (UInt128)(man >> (cbit + kcbitUInt128)); + bits[cu] = unchecked((UInt128)(man >> cbit)); + if (cbit > 0) + bits[cu - 1] = unchecked((UInt128)man) << (kcbitUInt128 - cbit); + + return sign > 0 ? new BigInteger(MemoryMarshal.Cast(bits), true) : -(new BigInteger(MemoryMarshal.Cast(bits))); + } + } // Floating /// /// Explicitly converts a value to a . @@ -1638,6 +1697,69 @@ public static implicit operator Octo(Int128 value) } return (Octo)(UInt128)value; } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Octo(BigInteger value) + { + Span bits = stackalloc byte[value.GetByteCount()]; + + int sign = value.Sign; + value.TryWriteBytes(bits, out int length); + scoped Span bits128; + length /= Unsafe.SizeOf(); + if (!BitOperations.IsPow2(bits.Length) && bits.Length >= 8) + { + int pow2Length = length * Unsafe.SizeOf(); + var remainder = bits[pow2Length..]; + bits128 = stackalloc UInt128[++length]; + bits[..pow2Length].CopyTo(MemoryMarshal.AsBytes(bits128)); + for (int i = remainder.Length - 1, shift = 128 - 8; i >= 0; i--, shift -= 8) + { + bits128[^1] = (UInt128)remainder[i] << shift; + } + } + else if (bits.Length < 4) + { + bits128 = stackalloc UInt128[length = 1]; + bits.CopyTo(MemoryMarshal.AsBytes(bits128)); + } + else + { + bits128 = MemoryMarshal.Cast(bits); + } + + if (length == 1) + { + return bits128[0]; + } + + // The maximum exponent for quads is 262143, which corresponds to a uint bit length of 128. + // All BigIntegers with bits[] longer than 128 evaluate to Quad.Infinity (or NegativeInfinity). + // Cases where the exponent is between 262144 and 262162 are handled in BitHelper.GetOctoFromParts. + const int InfinityLength = MaxExponent / 128; + + if (length > InfinityLength) + { + if (sign == 1) + return Octo.PositiveInfinity; + else + return Octo.NegativeInfinity; + } + + UInt256 h = bits128[^1]; + UInt256 m = length > 1 ? bits128[^2] : 0; + UInt256 l = length > 2 ? bits128[^3] : 0; + + int z = (int)UInt128.LeadingZeroCount((UInt128)h); + + int exp = (length - 2) * 128 - z; + UInt256 man = (h << 128 + z) | (m << z) | (l >> 128 - z); + + return BitHelper.GetOctoFromParts(sign, exp, man); + } // Floating /// /// Implicitly converts a value to a . diff --git a/src/MissingValues/Quad.Implementations.cs b/src/MissingValues/Quad.Implementations.cs index ef9f06f..5b3e7fb 100644 --- a/src/MissingValues/Quad.Implementations.cs +++ b/src/MissingValues/Quad.Implementations.cs @@ -354,6 +354,10 @@ public static Quad MinNumber(Quad x, Quad y) return IsNegative(x) ? x : y; } +#if NET9_0_OR_GREATER + static Quad INumberBase.MultiplyAddEstimate(Quad left, Quad right, Quad addend) => (left * right) + addend; +#endif + /// public static Quad Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) { @@ -502,6 +506,7 @@ private static bool TryConvertFrom(TOther value, out Quad result) Int128 actual => (Quad)actual, Int256 actual => (Quad)actual, Int512 actual => (Quad)actual, + BigInteger actual => (Quad)actual, _ => BitHelper.DefaultConvert(out converted) }; @@ -537,6 +542,7 @@ static bool INumberBase.TryConvertToChecked(Quad value, out TOther Int128 => (TOther)(object)(Int128)value, Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, + BigInteger => (TOther)(object)(BigInteger)value, nint => (TOther)(object)(nint)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -578,6 +584,7 @@ private static bool TryConvertTo(Quad value, out TOther result) Int256 => (TOther)(object)((value >= new Quad(0x40FE_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int256.MaxValue : (value <= new Quad(0xC0FE_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int256.MinValue : (Int256)value), Int512 => (TOther)(object)((value >= new Quad(0x41FE_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int512.MaxValue : (value <= new Quad(0xC1FE_0000_0000_0000, 0x0000_0000_0000_0000)) ? Int512.MinValue : (Int512)value), nint => (TOther)(object)((value >= nint.MaxValue) ? nint.MaxValue : (value <= nint.MinValue) ? nint.MinValue : (nint)value), + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; diff --git a/src/MissingValues/Quad.cs b/src/MissingValues/Quad.cs index 594b3fa..94562e5 100644 --- a/src/MissingValues/Quad.cs +++ b/src/MissingValues/Quad.cs @@ -8,6 +8,7 @@ using System.Globalization; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Text.Json.Serialization; @@ -1303,6 +1304,64 @@ public static explicit operator checked Int512(Quad value) return Int512.Zero; } } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is not finite. + public static explicit operator BigInteger(Quad value) + { + const int kcbitUlong = 64; + const int kcbitUInt128 = 128; + + if (!IsFinite(value)) + { + Thrower.IntegerOverflow(); + } + + BitHelper.GetQuadParts(value, out int sign, out int exp, out var man, out _); + + if (man == UInt128.Zero) + { + return BigInteger.Zero; + } + + if (exp <= 0) + { + if (exp <= -kcbitUInt128) + { + return BigInteger.Zero; + } + return (BigInteger)(sign < 0 ? -(man >> -exp) : (man >> -exp)); + } + else if (exp <= BiasedExponentLength) + { + return (BigInteger)(sign < 0 ? -(man << exp) : (man << exp)); + } + else + { + // Overflow into at least 3 ulongs. + // Move the leading 1 to the high bit. + man <<= BiasedExponentLength; + exp -= BiasedExponentLength; + + // Compute cu and cbit so that exp == 64 * cu - cbit and 0 <= cbit < 64. + int cu = (exp - 1) / kcbitUlong + 1; + int cbit = cu * kcbitUlong - exp; + Debug.Assert(0 <= cbit && cbit < kcbitUlong); + Debug.Assert(cu >= 1); + + // Populate the uints. + Span bits = stackalloc ulong[cu + 2]; + bits[cu + 1] = (ulong)(man >> (cbit + kcbitUlong)); + bits[cu] = unchecked((ulong)(man >> cbit)); + if (cbit > 0) + bits[cu - 1] = unchecked((ulong)man) << (kcbitUlong - cbit); + + return sign > 0 ? new BigInteger(MemoryMarshal.Cast(bits), true) : -(new BigInteger(MemoryMarshal.Cast(bits))); + } + } // Floating /// /// Explicitly converts a value to a . @@ -1590,6 +1649,69 @@ public static implicit operator Quad(Int128 value) } return (Quad)(UInt128)value; } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Quad(BigInteger value) + { + Span bits = stackalloc byte[value.GetByteCount()]; + + int sign = value.Sign; + value.TryWriteBytes(bits, out int length); + scoped Span bits64; + length /= sizeof(ulong); + if (!BitOperations.IsPow2(bits.Length) && bits.Length >= 4) + { + int pow2Length = length * sizeof(ulong); + var remainder = bits[pow2Length..]; + bits64 = stackalloc ulong[++length]; + bits[..pow2Length].CopyTo(MemoryMarshal.AsBytes(bits64)); + for (int i = remainder.Length - 1, shift = 64 - 8; i >= 0; i--, shift -= 8) + { + bits64[^1] = (ulong)remainder[i] << shift; + } + } + else if (bits.Length < 4) + { + bits64 = stackalloc ulong[length = 1]; + bits.CopyTo(MemoryMarshal.AsBytes(bits64)); + } + else + { + bits64 = MemoryMarshal.Cast(bits); + } + + if (length == 1) + { + return bits64[0]; + } + + // The maximum exponent for quads is 16383, which corresponds to a uint bit length of 64. + // All BigIntegers with bits[] longer than 64 evaluate to Quad.Infinity (or NegativeInfinity). + // Cases where the exponent is between 16384 and 16398 are handled in BitHelper.GetQuadFromParts. + const int InfinityLength = MaxExponent / 64; + + if (length > InfinityLength) + { + if (sign == 1) + return Quad.PositiveInfinity; + else + return Quad.NegativeInfinity; + } + + UInt128 h = bits64[^1]; + UInt128 m = length > 1 ? bits64[^2] : 0; + UInt128 l = length > 2 ? bits64[^3] : 0; + + int z = BitOperations.LeadingZeroCount((ulong)h); + + int exp = (length - 2) * 64 - z; + UInt128 man = (h << 64 + z) | (m << z) | (l >> 64 - z); + + return BitHelper.GetQuadFromParts(sign, exp, man); + } // Floating /// /// Explicitly converts a value to a . diff --git a/src/MissingValues/UInt256.Implementations.cs b/src/MissingValues/UInt256.Implementations.cs index 959ffb4..044c3f5 100644 --- a/src/MissingValues/UInt256.Implementations.cs +++ b/src/MissingValues/UInt256.Implementations.cs @@ -21,6 +21,7 @@ namespace MissingValues IBigInteger, IMinMaxValue, IUnsignedNumber, + IPowerFunctions, IFormattableUnsignedInteger { static UInt256 INumberBase.One => One; @@ -293,6 +294,10 @@ unsafe static void DivRemSlow(in UInt256 quotient, in UInt256 divisor, out UInt2 static UInt256 INumberBase.MaxMagnitudeNumber(UInt256 x, UInt256 y) => Max(x, y); +#if NET9_0_OR_GREATER + static UInt256 INumberBase.MultiplyAddEstimate(UInt256 left, UInt256 right, UInt256 addend) => (left * right) + addend; +#endif + /// public static UInt256 Min(UInt256 x, UInt256 y) => (x <= y) ? x : y; @@ -372,6 +377,8 @@ public static UInt256 Parse(ReadOnlySpan utf8Text, IFormatProvider? provid /// public static UInt256 PopCount(UInt256 value) => (UInt256)BitHelper.PopCount(in value); + static UInt256 IPowerFunctions.Pow(UInt256 x, UInt256 y) => Pow(x, checked((int)y)); + /// public static UInt256 RotateLeft(UInt256 value, int rotateAmount) => (value << rotateAmount) | (value >>> (256 - rotateAmount)); @@ -595,6 +602,7 @@ private static bool TryConvertFromChecked(TOther value, out UInt256 resu Int128 actual => (UInt256)actual, Int256 actual => (UInt256)actual, Int512 actual => (UInt256)actual, + BigInteger actual => (UInt256)actual, _ => BitHelper.DefaultConvert(out converted) }; } @@ -630,6 +638,7 @@ private static bool TryConvertFromSaturating(TOther value, out UInt256 r Int128 actual => (actual < 0) ? MinValue : (UInt256)actual, Int256 actual => (actual < 0) ? MinValue : (UInt256)actual, Int512 actual => (actual < 0) ? MinValue : (actual > (Int512)MaxValue) ? MaxValue : (UInt256)actual, + BigInteger actual => (BigInteger.IsNegative(actual)) ? MinValue : (actual > (BigInteger)MaxValue) ? MaxValue : (UInt256)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -662,6 +671,7 @@ private static bool TryConvertFromTruncating(TOther value, out UInt256 r Int128 actual => (actual < 0) ? MinValue : (UInt256)actual, Int256 actual => (actual < 0) ? MinValue : (UInt256)actual, Int512 actual => (actual < 0) ? MinValue : (UInt256)actual, + BigInteger actual => (BigInteger.IsNegative(actual)) ? MinValue : (UInt256)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -696,6 +706,7 @@ static bool INumberBase.TryConvertToChecked(UInt256 value, out Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; } @@ -738,6 +749,7 @@ static bool INumberBase.TryConvertToSaturating(UInt256 value, o #else nint => (TOther)(object)((value >= new UInt128(0x0000_0000_0000_0000, 0x7FFF_FFFF_FFFF_FFFF)) ? nint.MaxValue : (nint)value), #endif + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -771,6 +783,7 @@ static bool INumberBase.TryConvertToTruncating(UInt256 value, o Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -942,6 +955,12 @@ internal static int CountDigits(in UInt256 value) return digits; } static int IFormattableUnsignedInteger.CountDigits(in UInt256 value) => CountDigits(in value); + static int IFormattableInteger.UnsignedCompare(in UInt256 value1, in UInt256 value2) + { + if (value1 < value2) return -1; + else if (value1 > value2) return 1; + else return 0; + } /// public static UInt256 operator +(in UInt256 value) => value; @@ -1129,7 +1148,7 @@ internal static int CountDigits(in UInt256 value) leftSpan[..(UIntCount - (BitHelper.LeadingZeroCount(in left) / 32))], rightSpan[..(UIntCount - (BitHelper.LeadingZeroCount(in right) / 32))], rawBits); - var overflowBits = rawBits[8..]; + var overflowBits = rawBits[UIntCount..]; for (int i = 0; i < overflowBits.Length; i++) { diff --git a/src/MissingValues/UInt256.cs b/src/MissingValues/UInt256.cs index 707b29b..74db61f 100644 --- a/src/MissingValues/UInt256.cs +++ b/src/MissingValues/UInt256.cs @@ -1,6 +1,7 @@ using MissingValues.Info; using MissingValues.Internals; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -144,7 +145,103 @@ public static UInt256 BigMul(UInt256 left, UInt256 right, out UInt256 lower) rawBits); lower = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(rawBits))); - return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(rawBits), 8))); + return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(rawBits), UIntCount))); + } + + /// + /// Raises a value to the power of a specified value. + /// + /// The number to raise to the power. + /// The exponent to raise by. + /// The result of raising to the power. + /// is negative. + /// + /// The result of raising to the power is less than or greater than . + /// + public static UInt256 Pow(UInt256 value, int exponent) + { + const int UIntCount = Size / sizeof(uint); + + ArgumentOutOfRangeException.ThrowIfNegative(exponent); + + if (exponent == 0) + { + return One; + } + if (exponent == 1) + { + return value; + } + + uint power = checked((uint)exponent); + int size; + uint[]? bitsArray = null; + scoped Span bits; + + if (value._p3 == 0 && value._p2 == 0 && value._p1 == 0 && value._p0 <= uint.MaxValue) + { + if (value._p0 == 1) + return value; + if (value._p0 == 0) + return value; + + if (power >= (Size * 8)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + size = Calculator.PowBound(power, 1); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(unchecked((uint)value._p0), power, bits[..size]); + } + else + { + if (power >= (Size * 8)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + int valueLength = (UIntCount - (BitHelper.LeadingZeroCount(in value) / 32)); + size = Calculator.PowBound(power, valueLength); + + Span valueSpan = stackalloc uint[UIntCount]; + valueSpan.Clear(); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)), value); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(valueSpan[..valueLength], power, bits[..size]); + } + + if (size > UIntCount) + { + Span overflow = bits[UIntCount..]; + + for (int i = 0; i < overflow.Length; i++) + { + if (overflow[i] != 0) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + } + } + + UInt256 result = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(bits[..UIntCount]))); + + if (bitsArray is not null) + { + ArrayPool.Shared.Return(bitsArray); + } + + return result; } /// Parses a span of characters into a value. @@ -443,6 +540,21 @@ public static explicit operator checked nint(in UInt256 value) } return (nint)value._p0; } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator BigInteger(in UInt256 value) + { + if (value._p3 == 0 && value._p2 == 0 && value._p1 == 0) + { + return new BigInteger(value._p0); + } + Span span = stackalloc byte[Size]; + value.WriteLittleEndianUnsafe(span); + return new BigInteger(span, true); + } // Floating /// /// Explicitly converts a value to a . @@ -806,7 +918,76 @@ public static explicit operator checked UInt256(nint value) Thrower.IntegerOverflow(); } return new(0, (UInt128)value); - } + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt256(BigInteger value) + { + Span span = stackalloc byte[value.GetByteCount()]; + value.TryWriteBytes(span, out int bytesWritten, true); + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + UInt256 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + UInt256 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + return result; + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt256(BigInteger value) + { + if (BigInteger.IsNegative(value)) + { + Thrower.IntegerOverflow(); + } + + Span span = stackalloc byte[Size]; + + if (!value.TryWriteBytes(span, out int bytesWritten, true)) + { + Thrower.IntegerOverflow(); + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten == Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + else if (bytesWritten > Size) + { + Thrower.IntegerOverflow(); + } + + UInt256 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + UInt256 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + return result; + } #endregion private static UInt256 ToUInt256(double value) diff --git a/src/MissingValues/UInt512.Implementations.cs b/src/MissingValues/UInt512.Implementations.cs index 94d78dc..67de8b6 100644 --- a/src/MissingValues/UInt512.Implementations.cs +++ b/src/MissingValues/UInt512.Implementations.cs @@ -19,6 +19,7 @@ public partial struct UInt512 : IBigInteger, IMinMaxValue, IUnsignedNumber, + IPowerFunctions, IFormattableUnsignedInteger { static UInt512 INumberBase.One => One; @@ -309,6 +310,10 @@ int IBinaryInteger.GetShortestBitLength() static UInt512 INumberBase.MinMagnitudeNumber(UInt512 x, UInt512 y) => Min(x, y); +#if NET9_0_OR_GREATER + static UInt512 INumberBase.MultiplyAddEstimate(UInt512 left, UInt512 right, UInt512 addend) => (left * right) + addend; +#endif + /// public static UInt512 Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider) { @@ -379,6 +384,8 @@ public static UInt512 Parse(ReadOnlySpan utf8Text, IFormatProvider? provid /// public static UInt512 PopCount(UInt512 value) => (UInt512)(BitHelper.PopCount(in value)); + static UInt512 IPowerFunctions.Pow(UInt512 x, UInt512 y) => Pow(x, checked((int)y)); + /// public static UInt512 RotateLeft(UInt512 value, int rotateAmount) => (value << rotateAmount) | (value >>> (512 - rotateAmount)); @@ -629,6 +636,7 @@ private static bool TryConvertFromChecked(TOther value, out UInt512 resu Int128 actual => (UInt512)actual, Int256 actual => (UInt512)actual, Int512 actual => (UInt512)actual, + BigInteger actual => (UInt512)actual, _ => BitHelper.DefaultConvert(out converted) }; } @@ -664,6 +672,7 @@ private static bool TryConvertFromSaturating(TOther value, out UInt512 r Int128 actual => (actual < 0) ? MinValue : (UInt512)actual, Int256 actual => (actual < 0) ? MinValue : (UInt512)actual, Int512 actual => (actual < 0) ? MinValue : (UInt512)actual, + BigInteger actual => (BigInteger.IsNegative(actual)) ? MinValue : (actual > (BigInteger)MaxValue) ? MaxValue : (UInt512)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -695,6 +704,7 @@ private static bool TryConvertFromTruncating(TOther value, out UInt512 r Int128 actual => (actual < 0) ? MinValue : (UInt512)actual, Int256 actual => (actual < 0) ? MinValue : (UInt512)actual, Int512 actual => (actual < 0) ? MinValue : (UInt512)actual, + BigInteger actual => (BigInteger.IsNegative(actual)) ? MinValue : (UInt512)actual, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -729,6 +739,7 @@ static bool INumberBase.TryConvertToChecked(UInt512 value, out Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; } @@ -771,6 +782,7 @@ static bool INumberBase.TryConvertToSaturating(UInt512 value, o #else nint => (TOther)(object)((value >= new UInt128(0x0000_0000_0000_0000, 0x7FFF_FFFF_FFFF_FFFF)) ? nint.MaxValue : (nint)value), #endif + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; @@ -804,6 +816,7 @@ static bool INumberBase.TryConvertToTruncating(UInt512 value, o Int256 => (TOther)(object)(Int256)value, Int512 => (TOther)(object)(Int512)value, nint => (TOther)(object)(nint)value, + BigInteger => (TOther)(object)(BigInteger)value, _ => BitHelper.DefaultConvert(out converted) }; return converted; @@ -940,6 +953,13 @@ internal static int CountDigits(in UInt512 value) return digits; } + static int IFormattableInteger.UnsignedCompare(in UInt512 value1, in UInt512 value2) + { + if (value1 < value2) return -1; + else if (value1 > value2) return 1; + else return 0; + } + /// public static UInt512 operator +(in UInt512 value) => value; @@ -1180,7 +1200,7 @@ internal static int CountDigits(in UInt512 value) leftSpan[..(UIntCount - (BitHelper.LeadingZeroCount(in left) / 32))], rightSpan[..(UIntCount - (BitHelper.LeadingZeroCount(in right) / 32))], rawBits); - var overflowBits = rawBits[16..]; + var overflowBits = rawBits[UIntCount..]; for (int i = 0; i < overflowBits.Length; i++) { diff --git a/src/MissingValues/UInt512.cs b/src/MissingValues/UInt512.cs index e47e375..ee43140 100644 --- a/src/MissingValues/UInt512.cs +++ b/src/MissingValues/UInt512.cs @@ -1,11 +1,13 @@ using MissingValues.Info; using MissingValues.Internals; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; @@ -202,7 +204,103 @@ public static UInt512 BigMul(UInt512 left, UInt512 right, out UInt512 lower) rawBits); lower = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(rawBits))); - return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(rawBits), 16))); + return Unsafe.ReadUnaligned(ref Unsafe.As(ref Unsafe.Add(ref MemoryMarshal.GetReference(rawBits), UIntCount))); + } + + /// + /// Raises a value to the power of a specified value. + /// + /// The number to raise to the power. + /// The exponent to raise by. + /// The result of raising to the power. + /// is negative. + /// + /// The result of raising to the power is less than or greater than . + /// + public static UInt512 Pow(UInt512 value, int exponent) + { + const int UIntCount = Size / sizeof(uint); + + ArgumentOutOfRangeException.ThrowIfNegative(exponent); + + if (exponent == 0) + { + return One; + } + if (exponent == 1) + { + return value; + } + + uint power = checked((uint)exponent); + int size; + uint[]? bitsArray = null; + scoped Span bits; + + if (value._p7 == 0 && value._p6 == 0 && value._p5 == 0 && value._p4 == 0 && value._p3 == 0 && value._p2 == 0 && value._p1 == 0 && value._p0 <= uint.MaxValue) + { + if (value._p0 == 1) + return value; + if (value._p0 == 0) + return value; + + if (power >= (Size * 8)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + size = Calculator.PowBound(power, 1); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(unchecked((uint)value._p0), power, bits[..size]); + } + else + { + if (power >= (Size * 8)) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + + int valueLength = (UIntCount - (BitHelper.LeadingZeroCount(in value) / 32)); + size = Calculator.PowBound(power, valueLength); + + Span valueSpan = stackalloc uint[UIntCount]; + valueSpan.Clear(); + Unsafe.WriteUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(valueSpan)), value); + + bits = (size <= Calculator.StackAllocThreshold + ? stackalloc uint[Calculator.StackAllocThreshold] + : bitsArray = ArrayPool.Shared.Rent(size)); + bits.Clear(); + + Calculator.Pow(valueSpan[..valueLength], power, bits[..size]); + } + + if (size > UIntCount) + { + Span overflow = bits[UIntCount..]; + + for (int i = 0; i < overflow.Length; i++) + { + if (overflow[i] != 0) + { + Thrower.ArithmethicOverflow(Thrower.ArithmethicOperation.Exponentiation); + } + } + } + + UInt512 result = Unsafe.ReadUnaligned(ref Unsafe.As(ref MemoryMarshal.GetReference(bits[..UIntCount]))); + + if (bitsArray is not null) + { + ArrayPool.Shared.Return(bitsArray); + } + + return result; } /// Parses a span of characters into a value. @@ -514,6 +612,21 @@ public static explicit operator checked nint(in UInt512 value) } return checked((nint)value._p0); } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator BigInteger(in UInt512 value) + { + if (value._p7 == 0 && value._p6 == 0 && value._p5 == 0 && value._p4 == 0 && value._p3 == 0 && value._p2 == 0 && value._p1 == 0) + { + return new BigInteger(value._p0); + } + Span span = stackalloc byte[Size]; + value.WriteLittleEndianUnsafe(span); + return new BigInteger(span, true); + } // Floating /// /// Explicitly converts a value to a . @@ -817,6 +930,75 @@ public static explicit operator checked UInt512(nint value) } return new UInt512((nuint)value); } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt512(BigInteger value) + { + Span span = stackalloc byte[value.GetByteCount()]; + value.TryWriteBytes(span, out int bytesWritten, true); + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten >= Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + + UInt512 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + UInt512 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + return result; + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt512(BigInteger value) + { + if (BigInteger.IsNegative(value)) + { + Thrower.IntegerOverflow(); + } + + Span span = stackalloc byte[Size]; + + if (!value.TryWriteBytes(span, out int bytesWritten, true)) + { + Thrower.IntegerOverflow(); + } + + ref byte sourceRef = ref MemoryMarshal.GetReference(span); + + if (bytesWritten == Size) + { + return Unsafe.ReadUnaligned(ref sourceRef); + } + else if (bytesWritten > Size) + { + Thrower.IntegerOverflow(); + } + + UInt512 result = Zero; + + for (int i = 0; i < bytesWritten; i++) + { + UInt512 part = Unsafe.Add(ref sourceRef, i); + part <<= (i * 8); + result |= part; + } + + return result; + } //Floating /// /// Explicitly converts a value to a .