From 6b656b90e306db152fa6fd28b32b9a085b0799c2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 7 Jul 2023 10:06:07 +0200 Subject: [PATCH] Attempt to implement factorial calculation for generic math --- FastRng/HugeInteger.cs | 555 ++++++++++++++++++++++++++++++++++++ FastRng/IntegerMathTools.cs | 29 ++ 2 files changed, 584 insertions(+) create mode 100644 FastRng/HugeInteger.cs create mode 100644 FastRng/IntegerMathTools.cs diff --git a/FastRng/HugeInteger.cs b/FastRng/HugeInteger.cs new file mode 100644 index 0000000..561a20a --- /dev/null +++ b/FastRng/HugeInteger.cs @@ -0,0 +1,555 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Numerics; + +namespace FastRng; + +public struct HugeInteger : + IBinaryInteger>, + IComparisonOperators, TOtherNum, HugeInteger>, + IMultiplyOperators, TOtherNum, HugeInteger> + where TOtherNum : IBinaryInteger +{ + private ulong value = default; + + public HugeInteger(ulong value) => this.value = value; + + #region IBinaryInteger + + #region Implementation of IComparable + + /// + public int CompareTo(object obj) => this.value.CompareTo(obj); + + #endregion + + #region Implementation of IComparable + + /// + public int CompareTo(HugeInteger other) => this.value.CompareTo(other.value); + + #endregion + + #region Implementation of IEquatable + + /// + public bool Equals(HugeInteger other) => this.value.Equals(other.value); + + #endregion + + #region Implementation of IFormattable + + /// + public string ToString(string format, IFormatProvider formatProvider) => this.value.ToString(format, formatProvider); + + #endregion + + #region Implementation of IParsable + + /// + public static HugeInteger Parse(string s, IFormatProvider provider) => new(ulong.Parse(s, provider)); + + /// + public static bool TryParse(string s, IFormatProvider provider, out HugeInteger result) + { + if (ulong.TryParse(s, NumberStyles.Integer, provider, out var value)) + { + result = new HugeInteger(value); + return true; + } + + result = default; + return false; + } + + #endregion + + #region Implementation of ISpanFormattable + + /// + public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider provider) => this.value.TryFormat(destination, out charsWritten, format, provider); + + #endregion + + #region Implementation of ISpanParsable + + /// + public static HugeInteger Parse(ReadOnlySpan s, IFormatProvider provider) => new(ulong.Parse(s, provider)); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider provider, out HugeInteger result) + { + if (ulong.TryParse(s, NumberStyles.Integer, provider, out var value)) + { + result = new HugeInteger(value); + return true; + } + + result = default; + return false; + } + + #endregion + + #region Implementation of IAdditionOperators + + /// + public static HugeInteger operator +(HugeInteger left, HugeInteger right) => new(left.value + right.value); + + #endregion + + #region Implementation of IAdditiveIdentity + + /// + public static HugeInteger AdditiveIdentity => new(0); + + #endregion + + #region Implementation of IBitwiseOperators + + /// + public static HugeInteger operator &(HugeInteger left, HugeInteger right) => new(left.value & right.value); + + /// + public static HugeInteger operator |(HugeInteger left, HugeInteger right) => new(left.value | right.value); + + /// + public static HugeInteger operator ^(HugeInteger left, HugeInteger right) => new(left.value ^ right.value); + + /// + public static HugeInteger operator ~(HugeInteger value) => new(~value.value); + + #endregion + + #region Implementation of IEqualityOperators + + /// + public static bool operator ==(HugeInteger left, HugeInteger right) => left.value == right.value; + + /// + public static bool operator !=(HugeInteger left, HugeInteger right) => left.value != right.value; + + #endregion + + #region Implementation of IComparisonOperators + + /// + public static bool operator >(HugeInteger left, HugeInteger right) => left.value > right.value; + + /// + public static bool operator >=(HugeInteger left, HugeInteger right) => left.value >= right.value; + + /// + public static bool operator <(HugeInteger left, HugeInteger right) => left.value < right.value; + + /// + public static bool operator <=(HugeInteger left, HugeInteger right) => left.value <= right.value; + + #endregion + + #region Implementation of IDecrementOperators + + /// + public static HugeInteger operator --(HugeInteger value) + { + value.value--; + return value; + } + + #endregion + + #region Implementation of IDivisionOperators + + /// + public static HugeInteger operator /(HugeInteger left, HugeInteger right) => new(left.value / right.value); + + #endregion + + #region Implementation of IIncrementOperators + + /// + public static HugeInteger operator ++(HugeInteger value) + { + value.value++; + return value; + } + + #endregion + + #region Implementation of IModulusOperators + + /// + public static HugeInteger operator %(HugeInteger left, HugeInteger right) => new(left.value % right.value); + + #endregion + + #region Implementation of IMultiplicativeIdentity + + /// + public static HugeInteger MultiplicativeIdentity => new(1); + + #endregion + + #region Implementation of IMultiplyOperators + + /// + public static HugeInteger operator *(HugeInteger left, HugeInteger right) => new(left.value * right.value); + + #endregion + + #region Implementation of ISubtractionOperators + + /// + public static HugeInteger operator -(HugeInteger left, HugeInteger right) => new(left.value - right.value); + + #endregion + + #region Implementation of IUnaryNegationOperators + + /// + public static HugeInteger operator -(HugeInteger value) => throw new NotImplementedException(); + + #endregion + + #region Implementation of IUnaryPlusOperators + + /// + public static HugeInteger operator +(HugeInteger value) => throw new NotImplementedException(); + + #endregion + + #region Implementation of INumberBase + + /// + public static HugeInteger Abs(HugeInteger value) => value; + + /// + public static bool IsCanonical(HugeInteger value) => true; + + /// + public static bool IsComplexNumber(HugeInteger value) => false; + + /// + public static bool IsEvenInteger(HugeInteger value) => ulong.IsEvenInteger(value.value); + + /// + public static bool IsFinite(HugeInteger value) => true; + + /// + public static bool IsImaginaryNumber(HugeInteger value) => false; + + /// + public static bool IsInfinity(HugeInteger value) => false; + + /// + public static bool IsInteger(HugeInteger value) => true; + + /// + public static bool IsNaN(HugeInteger value) => false; + + /// + public static bool IsNegative(HugeInteger value) => false; + + /// + public static bool IsNegativeInfinity(HugeInteger value) => false; + + /// + public static bool IsNormal(HugeInteger value) => true; + + /// + public static bool IsOddInteger(HugeInteger value) => ulong.IsOddInteger(value.value); + + /// + public static bool IsPositive(HugeInteger value) => true; + + /// + public static bool IsPositiveInfinity(HugeInteger value) => false; + + /// + public static bool IsRealNumber(HugeInteger value) => true; + + /// + public static bool IsSubnormal(HugeInteger value) => false; + + /// + public static bool IsZero(HugeInteger value) => value.value == 0; + + /// + public static HugeInteger MaxMagnitude(HugeInteger x, HugeInteger y) => x.value > y.value ? x : y; + + /// + public static HugeInteger MaxMagnitudeNumber(HugeInteger x, HugeInteger y) => x.value > y.value ? x : y; + + /// + public static HugeInteger MinMagnitude(HugeInteger x, HugeInteger y) => x.value < y.value ? x : y; + + /// + public static HugeInteger MinMagnitudeNumber(HugeInteger x, HugeInteger y) => x.value < y.value ? x : y; + + /// + public static HugeInteger Parse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider) => new(ulong.Parse(s, style, provider)); + + /// + public static HugeInteger Parse(string s, NumberStyles style, IFormatProvider provider) => new(ulong.Parse(s, style, provider)); + + /// + public static bool TryConvertFromChecked(TOther value, out HugeInteger result) where TOther : INumberBase + { + result = new HugeInteger(ulong.CreateChecked(value)); + return true; + } + + /// + public static bool TryConvertFromSaturating(TOther value, out HugeInteger result) where TOther : INumberBase + { + result = new HugeInteger(ulong.CreateSaturating(value)); + return true; + } + + /// + public static bool TryConvertFromTruncating(TOther value, out HugeInteger result) where TOther : INumberBase + { + result = new HugeInteger(ulong.CreateTruncating(value)); + return true; + } + + /// + public static bool TryConvertToChecked(HugeInteger value, out TOther result) where TOther : INumberBase + { + result = TOther.CreateChecked(value.value); + return true; + } + + /// + public static bool TryConvertToSaturating(HugeInteger value, out TOther result) where TOther : INumberBase + { + result = TOther.CreateSaturating(value.value); + return true; + } + + /// + public static bool TryConvertToTruncating(HugeInteger value, out TOther result) where TOther : INumberBase + { + result = TOther.CreateTruncating(value.value); + return true; + } + + /// + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider provider, out HugeInteger result) + { + var state = ulong.TryParse(s, style, provider, out var value); + if(state) + result = new HugeInteger(value); + else + result = default; + + return state; + } + + /// + public static bool TryParse(string s, NumberStyles style, IFormatProvider provider, out HugeInteger result) + { + var state = ulong.TryParse(s, style, provider, out var value); + if(state) + result = new HugeInteger(value); + else + result = default; + + return state; + } + + /// + public static HugeInteger One => new(1); + + /// + public static int Radix => 2; + + /// + public static HugeInteger Zero => new(0); + + #endregion + + #region Implementation of IBinaryNumber + + /// + public static bool IsPow2(HugeInteger value) => ulong.IsPow2(value.value); + + /// + public static HugeInteger Log2(HugeInteger value) => new(ulong.Log2(value.value)); + + #endregion + + #region Implementation of IShiftOperators + + /// + public static HugeInteger operator <<(HugeInteger value, int shiftAmount) => new(value.value << shiftAmount); + + /// + public static HugeInteger operator >> (HugeInteger value, int shiftAmount) => new(value.value >> shiftAmount); + + /// + public static HugeInteger operator >>> (HugeInteger value, int shiftAmount) => new(value.value >>> shiftAmount); + + #endregion + + #region Implementation of IBinaryInteger + + /// + public int GetByteCount() => sizeof(ulong); + + /// + public int GetShortestBitLength() => (sizeof(ulong) * 8) - BitOperations.LeadingZeroCount(this.value); + + /// + public static HugeInteger PopCount(HugeInteger value) => new((ulong)BitOperations.PopCount(value.value)); + + /// + public static HugeInteger TrailingZeroCount(HugeInteger value) => new((ulong)BitOperations.TrailingZeroCount(value.value)); + + /// + public static bool TryReadBigEndian(ReadOnlySpan source, bool isUnsigned, out HugeInteger value) + { + if (!isUnsigned) + { + value = default; + return false; + } + + if(source.Length != sizeof(ulong)) + { + value = default; + return false; + } + + var sourceBytes = source.ToArray(); + if (BitConverter.IsLittleEndian) + sourceBytes = sourceBytes.Reverse().ToArray(); + + value = new HugeInteger(BitConverter.ToUInt64(sourceBytes)); + return true; + } + + /// + public static bool TryReadLittleEndian(ReadOnlySpan source, bool isUnsigned, out HugeInteger value) + { + if (!isUnsigned) + { + value = default; + return false; + } + + if(source.Length != sizeof(ulong)) + { + value = default; + return false; + } + + var sourceBytes = source.ToArray(); + if (!BitConverter.IsLittleEndian) + sourceBytes = sourceBytes.Reverse().ToArray(); + + value = new HugeInteger(BitConverter.ToUInt64(sourceBytes)); + return true; + } + + /// + public bool TryWriteBigEndian(Span destination, out int bytesWritten) + { + var bytes = BitConverter.GetBytes(this.value); + if (BitConverter.IsLittleEndian) + bytes = bytes.Reverse().ToArray(); + + bytes.CopyTo(destination); + bytesWritten = bytes.Length; + return true; + } + + /// + public bool TryWriteLittleEndian(Span destination, out int bytesWritten) + { + var bytes = BitConverter.GetBytes(this.value); + if (!BitConverter.IsLittleEndian) + bytes = bytes.Reverse().ToArray(); + + bytes.CopyTo(destination); + bytesWritten = bytes.Length; + return true; + } + + #endregion + + #endregion + + #region Necessary extra operators + + #region Implementation of IEqualityOperators,TOtherNum,HugeInteger> + + /// + public static HugeInteger operator ==(HugeInteger left, TOtherNum right) + { + return left == right; + } + + /// + public static HugeInteger operator !=(HugeInteger left, TOtherNum right) + { + return left != right; + } + + #endregion + + #region Implementation of IComparisonOperators,TOtherNum,HugeInteger> + + /// + public static HugeInteger operator >(HugeInteger left, TOtherNum right) + { + return left > right; + } + + /// + public static HugeInteger operator >=(HugeInteger left, TOtherNum right) + { + return left >= right; + } + + /// + public static HugeInteger operator <(HugeInteger left, TOtherNum right) + { + return left < right; + } + + /// + public static HugeInteger operator <=(HugeInteger left, TOtherNum right) + { + return left <= right; + } + + #endregion + + #region Implementation of IMultiplyOperators,TOtherNum,HugeInteger> + + /// + public static HugeInteger operator *(HugeInteger left, TOtherNum right) + { + // Would work, but TOtherNum might not be able to hold the result! Thus, we cannot convert + // left to TOtherNum, and we cannot convert right to HugeInteger. + // return new HugeInteger(ulong.CreateSaturating(TOtherNum.CreateSaturating(left.value) * right)); + + return left * right; + } + + #endregion + + #endregion + + /// + /// Converts a ulong to a HugeInteger. + /// + public static implicit operator HugeInteger(ulong value) => new(value); + + /// + /// Converts a HugeInteger to a ulong. + /// + public static implicit operator ulong(HugeInteger value) => value.value; +} \ No newline at end of file diff --git a/FastRng/IntegerMathTools.cs b/FastRng/IntegerMathTools.cs new file mode 100644 index 0000000..d023729 --- /dev/null +++ b/FastRng/IntegerMathTools.cs @@ -0,0 +1,29 @@ +using System; +using System.Numerics; + +namespace FastRng; + +/// +/// Provides some mathematical function, which are not available within in .NET itself. +/// +public static class IntegerMathTools where TNum : IBinaryInteger, IComparisonOperators, TNum, HugeInteger>, IMultiplyOperators, TNum, HugeInteger> +{ + private static readonly TNum CONST20 = TNum.CreateChecked(20); + + /// + /// The mathematical factorial function for integer numbers. + /// + /// The value, for which you want to know the factorial. + /// Throws, when x is greater than 20. Due to limitations of 64bit ulong type. + public static ulong Factorial(TNum x) + { + if (x > CONST20) + throw new ArgumentOutOfRangeException(nameof(x), $"Cannot compute {x}!, since ulong.max is 18_446_744_073_709_551_615."); + + var accumulator = new HugeInteger(1); + for (var factor = TNum.One; factor <= x; factor++) + accumulator *= factor; + + return accumulator; + } +} \ No newline at end of file