From a44d51e557c4549cd742104efdb1a918a910c98a Mon Sep 17 00:00:00 2001 From: Jim <0xJ1M@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:04:18 +0000 Subject: [PATCH] WIP --- .../MathEngine/AST/Nodes/NodeFactory.cs | 2 +- MathEngine/MathEngine/MMU/Memory.cs | 56 ++++ MathEngine/MathEngine/MathEngine.csproj | 7 + MathEngine/MathEngine/types/BigInteger.cs | 247 ++++++++++++++++ MathEngine/MathEngine/types/DecimalBuilder.cs | 264 ++++++++++++++++++ MathEngine/MathRunner/Program.cs | 9 +- MathEngine/common/UnmanagedMMU.dll | Bin 0 -> 9216 bytes 7 files changed, 583 insertions(+), 2 deletions(-) create mode 100644 MathEngine/MathEngine/MMU/Memory.cs create mode 100644 MathEngine/MathEngine/types/BigInteger.cs create mode 100644 MathEngine/MathEngine/types/DecimalBuilder.cs create mode 100644 MathEngine/common/UnmanagedMMU.dll diff --git a/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs b/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs index d7115cd..33109ff 100644 --- a/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs +++ b/MathEngine/MathEngine/AST/Nodes/NodeFactory.cs @@ -32,7 +32,7 @@ namespace MathEngine.AST.Nodes return new BinaryNode(LeftBranch, RightBranch, (a, b) => a / b); case TokenType.Exponentiation: throw new NotImplementedException("Exponentiation is not supported at this time!"); - return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b); + // return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b); default: throw new NotImplementedException("Attempted to create a BinaryNode with an invalid operation!"); } diff --git a/MathEngine/MathEngine/MMU/Memory.cs b/MathEngine/MathEngine/MMU/Memory.cs new file mode 100644 index 0000000..fe75030 --- /dev/null +++ b/MathEngine/MathEngine/MMU/Memory.cs @@ -0,0 +1,56 @@ +using System.Runtime.CompilerServices; +using System.Text; +using UnmanagedMMU; + + +namespace MathEngine.MemoryManagement +{ + internal unsafe class Memory + { + public static readonly SegmentedPool ScratchWorkspace; + + static Memory() + { + ScratchWorkspace = new SegmentedPool(initialSegments: 16, zeroMemory: true); + } + + /// + /// Copies a range of elements from the specified source buffer to the specified destination buffer. + /// + /// The buffer to copy elements from. + /// The zero-based index in at which copying begins. + /// The buffer to copy elements to. + /// The zero-based index in at which storing begins. + /// The number of elements to copy. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MemCopy(UInt32* source, int sourceIndex, UInt32* destination, int destinationIndex, int count) + { + // Calculate byte offsets from element indices + UInt32* srcPtr = source + sourceIndex; + UInt32* destPtr = destination + destinationIndex; + + Unsafe.CopyBlock(destPtr, srcPtr, (uint)(count * sizeof(UInt32))); + } + +#if DEBUG + /// + /// Prints the contents of the unmanaged memory block pointed to by + /// + /// * pointer to the memory block to print + /// The length of the memory block pointed to by + private static void PrintUnmanagedArray(T* arr, int len) where T : unmanaged + { + StringBuilder sb = new StringBuilder(); + sb.Append("["); + for (int i = 0; i < len - 1; i++) + { + sb.Append(arr[i]); + sb.Append(", "); + } + sb.Append(arr[len - 1]); + sb.Append("]"); + Console.WriteLine(sb.ToString()); + } +#endif + } +} diff --git a/MathEngine/MathEngine/MathEngine.csproj b/MathEngine/MathEngine/MathEngine.csproj index 5e22321..738b201 100644 --- a/MathEngine/MathEngine/MathEngine.csproj +++ b/MathEngine/MathEngine/MathEngine.csproj @@ -5,6 +5,7 @@ enable enable True + true @@ -21,4 +22,10 @@ + + + ..\common\UnmanagedMMU.dll + + + diff --git a/MathEngine/MathEngine/types/BigInteger.cs b/MathEngine/MathEngine/types/BigInteger.cs new file mode 100644 index 0000000..6ed18df --- /dev/null +++ b/MathEngine/MathEngine/types/BigInteger.cs @@ -0,0 +1,247 @@ +using MathEngine.MemoryManagement; + +namespace MathEngine.types +{ + public unsafe struct BigInteger + { + private UInt32* _num; + private int _len; + private int _sgn; + + private static BigInteger _zero = new BigInteger(0); + + public BigInteger(int val) + { + _num = Memory.ScratchWorkspace.AllocateAsPointer(16); + _len = 1; + if (val < 0) + { + _sgn = -1; + _num[0] = (UInt32)(-val); + return; + } + if (val > 0) + { + _sgn = 1; + _num[0] = (UInt32)(val); + return; + } + else + { + _sgn = 0; + _num[0] = 0; + return; + } + } + + public BigInteger(UInt32 val) + { + _num = Memory.ScratchWorkspace.AllocateAsPointer(16); + _len = 1; + if (val > 0) + { + _sgn = 1; + _num[0] = val; + return; + } + else + { + _sgn = 0; + _num[0] = 0; + return; + } + } + + private static unsafe int CompareAbs(in BigInteger X, in BigInteger Y) + { + if (X._len != Y._len) + return X._len.CompareTo(Y._len); + for (int i = X._len - 1; i >= 0; i--) + { + if (X._num[i] != Y._num[i]) + return X._num[i].CompareTo(Y._num[i]); + } + return 0; + } + + public static BigInteger Add(BigInteger X, BigInteger Y) + { + if (X._sgn == 0) + { + return Y; + } + if (Y._sgn == 0) + { + return X; + } + + if (X._sgn == Y._sgn) + { + int n = X._len; + int m = Y._len; + int len = Math.Max(n, m); + UInt32* res = Memory.ScratchWorkspace.AllocateAsPointer(len + 1); + + ulong carry = 0; + int i = 0; + for (; i < Math.Min(n, m); i++) + { + ulong sum = (ulong)X._num[i] + Y._num[i] + carry; + res[i] = (UInt32)sum; + carry = sum >> 32; + } + + UInt32* longer = n > m ? X._num : Y._num; + for (; i < len; i++) + { + ulong sum = (ulong)longer[i] + carry; + res[i] = (UInt32)sum; + carry = sum >> 32; + } + + if (carry != 0) + res[len++] = (UInt32)carry; + + BigInteger result; + result._num = res; + result._len = len; + result._sgn = X._sgn; + return result; + } + + int cmp = CompareAbs(X, Y); + BigInteger bigger, smaller; + int resultSign; + + if (cmp >= 0) + { + bigger = X; + smaller = Y; + resultSign = X._sgn; + } + else + { + bigger = Y; + smaller = X; + resultSign = Y._sgn; + } + + // Perform magnitude subtraction: bigger - smaller + int nB = bigger._len, nS = smaller._len; + UInt32* resSub = Memory.ScratchWorkspace.AllocateAsPointer(nB); + + long borrow = 0; + int iSub = 0; + for (; iSub < nS; iSub++) + { + long diff = (long)bigger._num[iSub] - smaller._num[iSub] - borrow; + if (diff < 0) + { + diff += 1L << 32; + borrow = 1; + } + else + { + borrow = 0; + } + resSub[iSub] = (UInt32)diff; + } + + for (; iSub < nB; iSub++) + { + long diff = (long)bigger._num[iSub] - borrow; + if (diff < 0) + { + diff += 1L << 32; + borrow = 1; + } + else + { + borrow = 0; + } + resSub[iSub] = (UInt32)diff; + } + + // Trim leading zeros + int lenSub = nB; + while (lenSub > 0 && resSub[lenSub - 1] == 0) lenSub--; + + BigInteger resultSub; + resultSub._num = resSub; + resultSub._len = lenSub; + resultSub._sgn = (lenSub == 0) ? 0 : resultSign; + return resultSub; + } + + public static BigInteger Subtract(BigInteger X, BigInteger Y) + { + BigInteger negY = Y; + negY._sgn = (Y._sgn == 0) ? 0 : -Y._sgn; + return Add(X, negY); + } + + public static BigInteger Multiply(BigInteger X, BigInteger Y) + { + int n = X._len, m = Y._len; + if (n == 0 || m == 0) + { + BigInteger zero; + zero._num = Memory.ScratchWorkspace.AllocateAsPointer(1); + zero._num[0] = 0; + zero._len = 0; + zero._sgn = 0; + return zero; + } + + int len = n + m; + UInt32* res = Memory.ScratchWorkspace.AllocateAsPointer(len); + for (int i = 0; i < len; i++) res[i] = 0; + + for (int i = 0; i < n; i++) + { + ulong carry = 0; + ulong xi = X._num[i]; + for (int j = 0; j < m; j++) + { + ulong prod = (ulong)res[i + j] + xi * Y._num[j] + carry; + res[i + j] = (UInt32)prod; + carry = prod >> 32; + } + res[i + m] = (UInt32)((ulong)res[i + m] + carry); + } + + // Trim leading zeros + int finalLen = len; + while (finalLen > 0 && res[finalLen - 1] == 0) finalLen--; + + BigInteger result; + result._num = res; + result._len = finalLen; + result._sgn = X._sgn * Y._sgn; + return result; + } + + + public static BigInteger Divide(BigInteger X, BigInteger Y) + { + if (Y == zero) + } + + public static BigInteger operator *(BigInteger X, BigInteger Y) + { + return Multiply(X, Y); + } + + public override string ToString() + { + // create a copy of the underlying number + // we want to keep immutability + if (_len == 0) + { + return "0"; + } + return DecimalBuilder.ConvertBigIntegerToString(_num, _len); + } + } + +} diff --git a/MathEngine/MathEngine/types/DecimalBuilder.cs b/MathEngine/MathEngine/types/DecimalBuilder.cs new file mode 100644 index 0000000..7761355 --- /dev/null +++ b/MathEngine/MathEngine/types/DecimalBuilder.cs @@ -0,0 +1,264 @@ +using MathEngine.MemoryManagement; +using System; +using System.Runtime.InteropServices.Marshalling; +using System.Text; + +namespace MathEngine.types +{ + /// + /// Class that converts a representation to its decimal base 10 representaiton + /// + internal unsafe static class DecimalBuilder + { + private const UInt32 DECBASE = 1_000_000_000; + private const int THRESHOLD = 8; + + + /// + /// Performs simple division of by base 10^9using Knuth's algorithm. + /// + /// Pointer to a LSW ordered Uint32 array containing the digits to divide + /// The length of the array pointed to by + /// When this function returns, contains the base 10^9 representation of + /// Then length of + private static int SimpleDiv(UInt32* x, int xLen, out UInt32* result) + { + // possible over allocation, oh well! + result = Memory.ScratchWorkspace.AllocateAsPointer(THRESHOLD); + int digitIndex = 0; + while (xLen > 0) + { + ulong rem = 0; + for (int i = xLen - 1; i >= 0; i--) + { + ulong cur = (rem << 32) + x[i]; + x[i] = (UInt32)(cur / DECBASE); + rem = cur % DECBASE; + } + + result[digitIndex] = (UInt32)rem; + digitIndex++; + + while (xLen > 0 && x[xLen - 1] == 0) + { + xLen--; + } + } + + if (digitIndex == 0) + { + result[0] = 0; + digitIndex++; + } + return digitIndex; + } + + /// + /// Performs multiplication of two of and in base 10^9. + /// + /// Pointer to a LSW ordered Uint32 array containing the digits to be multiplied by + /// The number of elements that contains + /// Pointer to a LSW ordered Uint32 array containing the digits to be multiplied by + /// The number of elements that contains + /// When this function returns, contains the base 10^9 representation of the multiplicaiton of and + /// Then length of + private static int DecMultiply(UInt32* x, int xLen, UInt32* y, int yLen, out UInt32* result) + { + int tmpLen = (xLen + yLen); + UInt64* tmp = Memory.ScratchWorkspace.AllocateAsPointer(xLen + yLen); + + for (int i = 0; i < xLen; i++) + { + ulong carry = 0; + for (int j = 0; j < yLen; j++) + { + ulong cur = tmp[i + j] + (ulong)x[i] * y[j] + carry; + tmp[i + j] = cur % DECBASE; + carry = cur / DECBASE; + } + int k = i + yLen; + while (carry != 0) + { + ulong cur = tmp[k] + carry; + tmp[k] = cur % DECBASE; + carry = cur / DECBASE; + k++; + } + } + + //convert to uint and trim leading zeros + result = Memory.ScratchWorkspace.AllocateAsPointer(tmpLen); + UInt32[] res = new UInt32[tmpLen]; + for (int i = 0; i < tmpLen; i++) + { + result[i] = (UInt32)(tmp[i]); + } + + int sz = res.Length; + while (sz > 1 && result[sz - 1] == 0) + { + sz--; + } + return sz; + } + + /// + /// Performs addition of two of and in base 10^9. + /// + /// Pointer to a LSW ordered Uint32 array containing the digits to be added to by + /// The number of elements that contains + /// Pointer to a LSW ordered Uint32 array containing the digits to be added to by + /// The number of elements that contains + /// When this function returns, contains the base 10^9 representation of the addition of and + /// Then length of + private static int DecAdd(UInt32* x, int xLen, UInt32* y, int yLen, out UInt32* result) + { + int sz = Math.Max(xLen, yLen); // Test against with xLen ^ ((xLen ^ yLen) & ((xLen - yLen) >> 31)) + + result = Memory.ScratchWorkspace.AllocateAsPointer(sz + 1); + + ulong carry = 0; + + for (int i = 0; i < sz; i++) + { + ulong xv = i < xLen ? x[i] : 0; + ulong yv = i < yLen ? y[i] : 0; + ulong cur = xv + yv + carry; + result[i] = (UInt32)(cur % DECBASE); + carry = cur / DECBASE; + } + + if (carry != 0) + { + result[sz] = (UInt32)carry; + return sz + 1; + } + //else + //{ + // Array.Resize(ref res, sz); //? + //} + return sz; + } + + /// + /// Performs addition of two of and in base 10^9. + /// + /// Pointer to a LSW ordered Uint32 array containing the digits to be added to by + /// The number of elements that contains + /// Pointer to a LSW ordered Uint32 array containing the digits to be added to by + /// The number of elements that contains + /// When this function returns, contains the base 10^9 representation of the addition of and + /// Then length of + private static int PowerBtoN(int k, out UInt32* result) + { + const ulong B = 1UL << 32; + result = Memory.ScratchWorkspace.AllocateAsPointer(1); + result[0] = 1; + int resultLen = 1; + + // repeat multiply by B k times; + // in prod this can be simplified + + for (int i = 0; i < k; i++) + { + resultLen = MultiplyByWord(result, resultLen, B, out UInt32* tResult); + // assign the multiplication result back into result and go again + result = tResult; + } + return resultLen; + } + + private static int MultiplyByWord(UInt32* x, int xLen, ulong b, out UInt32* result) + { + result = Memory.ScratchWorkspace.AllocateAsPointer(xLen + 2); + ulong carry = 0; + + for (int i = 0; i < xLen; i++) + { + ulong cur = (ulong)x[i] * b + carry; + result[i] = (UInt32)(cur % DECBASE); + carry = cur / DECBASE; + } + + int pos = xLen; + + while (carry != 0) + { + result[pos++] = (UInt32)(carry % DECBASE); + carry /= DECBASE; + } + + //trim zeros + int sz = pos; + while (sz > 1 && result[sz - 1] == 0) + { + sz--; + } + return sz; + } + + /// + /// Computes the base 10^9 representation of _num recursively if the number of digits is large + /// + /// Pointer to a LSW ordered Uint32 array containing the digits to convert + /// The length of the array pointed to by + /// When this function returns, contains the base 10^9 representation of + /// + private static int ToBase10_9(UInt32* digits, int len, out UInt32* result) + { + while (len > 0 && digits[len - 1] == 0) + { + len--; + } + + if (len == 0) + { + result = Memory.ScratchWorkspace.AllocateAsPointer(1); + result[0] = 0; + return 1; + } + if (len < THRESHOLD) + { + return SimpleDiv(digits, len, out result); + } + int n = len / 2; + UInt32* lo = Memory.ScratchWorkspace.AllocateAsPointer(n); + UInt32* hi = Memory.ScratchWorkspace.AllocateAsPointer(len - n); + Memory.MemCopy(digits, 0, lo, 0, n); + Memory.MemCopy(digits, n, hi, 0, len - n); + + int dloLen = ToBase10_9(lo, n, out UInt32* dlo); + int dhiLen = ToBase10_9(hi, len - n, out UInt32* dhi); + + int powLen = PowerBtoN(n, out UInt32* pow); + + int dhiScaledLen = DecMultiply(dhi, dhiLen, pow, powLen, out UInt32* dhiScaled); + return DecAdd(dhiScaled, dhiScaledLen, dlo, dloLen, out result); + } + + /// + /// + /// + /// + /// + /// + public static string ConvertBigIntegerToString(UInt32* num, int numLen) + { + + UInt32* cpy = Memory.ScratchWorkspace.AllocateAsPointer(numLen); + Memory.MemCopy(num, 0, cpy, 0, numLen); + int dLen = ToBase10_9(cpy, numLen, out UInt32* digits); + + StringBuilder sb = new(); + + sb.Append(digits[dLen - 1].ToString()); + + for (int i = dLen - 2; i >= 0; i--) + { + sb.Append(digits[i].ToString("D9")); + } + + return sb.ToString(); + } + } +} diff --git a/MathEngine/MathRunner/Program.cs b/MathEngine/MathRunner/Program.cs index 6f010bb..774441b 100644 --- a/MathEngine/MathRunner/Program.cs +++ b/MathEngine/MathRunner/Program.cs @@ -1,4 +1,5 @@ -using MathEngine.Expression; +using MathEngine.types; +using MathEngine.Expression; namespace MathRunner { @@ -6,6 +7,12 @@ namespace MathRunner { static void Main(string[] args) { + BigInteger x = new(123456); + for (int i = 0; i < 4; i++) + { + x = x * x; + } + Console.WriteLine(x.ToString()); Console.WriteLine("Evaluting expression..."); Expression exp = new("54+2+1"); Console.WriteLine(exp.Evaluate()); diff --git a/MathEngine/common/UnmanagedMMU.dll b/MathEngine/common/UnmanagedMMU.dll new file mode 100644 index 0000000000000000000000000000000000000000..0d235d0c96b23a6b1c38962a28aa797dca4d244b GIT binary patch literal 9216 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 axc!0)2ISvRo~hZve!&*}a=8CT5BwMXjzJm# literal 0 HcmV?d00001