This commit is contained in:
Jim
2026-03-19 19:04:18 +00:00
parent c527e59b57
commit a44d51e557
7 changed files with 583 additions and 2 deletions

View File

@@ -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!");
}

View File

@@ -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);
}
/// <summary>
/// Copies a range of elements from the specified source buffer to the specified destination buffer.
/// </summary>
/// <param name="source">The buffer to copy elements from.</param>
/// <param name="sourceIndex">The zero-based index in <paramref name="source"/> at which copying begins.</param>
/// <param name="destination">The buffer to copy elements to.</param>
/// <param name="destinationIndex">The zero-based index in <paramref name="destination"/> at which storing begins.</param>
/// <param name="count">The number of elements to copy.</param>
[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
/// <summary>
/// Prints the contents of the unmanaged <typeparamref name="T"/> memory block pointed to by <paramref name="arr"/>
/// </summary>
/// <param name="arr"><typeparamref name="T"/>* pointer to the memory block to print</param>
/// <param name="len">The length of the memory block pointed to by <paramref name="arr"/></param>
private static void PrintUnmanagedArray<T>(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
}
}

View File

@@ -5,6 +5,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
@@ -21,4 +22,10 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<Reference Include="UnmanagedMMU">
<HintPath>..\common\UnmanagedMMU.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@@ -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<UInt32>(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<UInt32>(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<UInt32>(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<UInt32>(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<UInt32>(1);
zero._num[0] = 0;
zero._len = 0;
zero._sgn = 0;
return zero;
}
int len = n + m;
UInt32* res = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(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);
}
}
}

View File

@@ -0,0 +1,264 @@
using MathEngine.MemoryManagement;
using System;
using System.Runtime.InteropServices.Marshalling;
using System.Text;
namespace MathEngine.types
{
/// <summary>
/// Class that converts a <see cref="BigInteger"/> representation to its decimal base 10 representaiton
/// </summary>
internal unsafe static class DecimalBuilder
{
private const UInt32 DECBASE = 1_000_000_000;
private const int THRESHOLD = 8;
/// <summary>
/// Performs simple division of <paramref name="x"/> by base 10^9using Knuth's algorithm.
/// </summary>
/// <param name="x">Pointer to a LSW ordered Uint32 array containing the digits to divide</param>
/// <param name="xLen">The length of the array pointed to by <paramref name="x"/></param>
/// <param name="result">When this function returns, contains the base 10^9 representation of <paramref name="x"/></param>
/// <returns>Then length of <paramref name="result"/> </returns>
private static int SimpleDiv(UInt32* x, int xLen, out UInt32* result)
{
// possible over allocation, oh well!
result = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(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;
}
/// <summary>
/// Performs multiplication of two of <paramref name="x"/> and <paramref name="y"/> in base 10^9.
/// </summary>
/// <param name="x">Pointer to a LSW ordered Uint32 array containing the digits to be multiplied by <paramref name="y"/></param>
/// <param name="xLen">The number of elements that <paramref name="x"/> contains</param>
/// <param name="y">Pointer to a LSW ordered Uint32 array containing the digits to be multiplied by <paramref name="x"/></param>
/// <param name="yLen">The number of elements that <paramref name="y"/> contains</param>
/// <param name="result">When this function returns, contains the base 10^9 representation of the multiplicaiton of <paramref name="x"/> and <paramref name="y"/></param>
/// <returns>Then length of <paramref name="result"/> </returns>
private static int DecMultiply(UInt32* x, int xLen, UInt32* y, int yLen, out UInt32* result)
{
int tmpLen = (xLen + yLen);
UInt64* tmp = Memory.ScratchWorkspace.AllocateAsPointer<UInt64>(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<UInt32>(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;
}
/// <summary>
/// Performs addition of two of <paramref name="x"/> and <paramref name="y"/> in base 10^9.
/// </summary>
/// <param name="x">Pointer to a LSW ordered Uint32 array containing the digits to be added to by <paramref name="y"/></param>
/// <param name="xLen">The number of elements that <paramref name="x"/> contains</param>
/// <param name="y">Pointer to a LSW ordered Uint32 array containing the digits to be added to by <paramref name="x"/></param>
/// <param name="yLen">The number of elements that <paramref name="y"/> contains</param>
/// <param name="result">When this function returns, contains the base 10^9 representation of the addition of <paramref name="x"/> and <paramref name="y"/></param>
/// <returns>Then length of <paramref name="result"/> </returns>
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<UInt32>(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;
}
/// <summary>
/// Performs addition of two of <paramref name="x"/> and <paramref name="y"/> in base 10^9.
/// </summary>
/// <param name="x">Pointer to a LSW ordered Uint32 array containing the digits to be added to by <paramref name="y"/></param>
/// <param name="xLen">The number of elements that <paramref name="x"/> contains</param>
/// <param name="y">Pointer to a LSW ordered Uint32 array containing the digits to be added to by <paramref name="x"/></param>
/// <param name="yLen">The number of elements that <paramref name="y"/> contains</param>
/// <param name="result">When this function returns, contains the base 10^9 representation of the addition of <paramref name="x"/> and <paramref name="y"/></param>
/// <returns>Then length of <paramref name="result"/> </returns>
private static int PowerBtoN(int k, out UInt32* result)
{
const ulong B = 1UL << 32;
result = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(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<UInt32>(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;
}
/// <summary>
/// Computes the base 10^9 representation of _num recursively if the number of digits is large
/// </summary>
/// <param name="digits">Pointer to a LSW ordered Uint32 array containing the digits to convert</param>
/// <param name="len">The length of the array pointed to by <paramref name="digits"/></param>
/// <param name="result">When this function returns, contains the base 10^9 representation of <paramref name="digits"/></param>
/// <returns></returns>
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<UInt32>(1);
result[0] = 0;
return 1;
}
if (len < THRESHOLD)
{
return SimpleDiv(digits, len, out result);
}
int n = len / 2;
UInt32* lo = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(n);
UInt32* hi = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(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);
}
/// <summary>
///
/// </summary>
/// <param name="num"></param>
/// <param name="numLen"></param>
/// <returns></returns>
public static string ConvertBigIntegerToString(UInt32* num, int numLen)
{
UInt32* cpy = Memory.ScratchWorkspace.AllocateAsPointer<UInt32>(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();
}
}
}

View File

@@ -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());

Binary file not shown.