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 0000000..0d235d0 Binary files /dev/null and b/MathEngine/common/UnmanagedMMU.dll differ