-
Notifications
You must be signed in to change notification settings - Fork 1
BigInt Implementation
This page documents how Asynkron.JsEngine implements JavaScript BigInt - arbitrary precision integers as specified in ES2020+.
flowchart TB
subgraph JsValue["JsValue (24 bytes)"]
Kind["Kind = BigInt (4)"]
NumberValue["NumberValue = 0.0"]
ObjectValue["ObjectValue → JsBigInt"]
end
subgraph JsBigInt["JsBigInt"]
Value["Value: BigInteger"]
Operators["Operators: +, -, *, /, %, **, &, |, ^, ~, <<, >>"]
end
subgraph SystemNumerics["System.Numerics"]
BigInteger["BigInteger\n(arbitrary precision)"]
end
ObjectValue --> JsBigInt
JsBigInt --> BigInteger
BigInt values are stored as JsBigInt objects that wrap .NET's System.Numerics.BigInteger:
File: JsTypes/JsBigInt.cs
public sealed class JsBigInt(BigInteger value) : IEquatable<JsBigInt>
{
public BigInteger Value { get; } = value;
// Pre-allocated constants to reduce allocations
public static JsBigInt Zero => new(BigInteger.Zero);
public static JsBigInt One => new(BigInteger.One);
}BigInt uses the ObjectValue field in JsValue:
File: JsTypes/JsValue.cs
/// <summary>Creates a BigInt value.</summary>
[MethodImpl(JsEngineConstants.Inlining)]
public JsValue(JsBigInt value)
{
Kind = JsValueKind.BigInt;
NumberValue = 0.0;
ObjectValue = value;
}
public bool IsBigInt
{
[MethodImpl(JsEngineConstants.Inlining)]
get => Kind == JsValueKind.BigInt;
}
[MethodImpl(JsEngineConstants.Inlining)]
public JsBigInt AsBigInt() => (JsBigInt)ObjectValue!;All arithmetic operators are implemented directly on JsBigInt:
File: JsTypes/JsBigInt.cs
| Operator | Implementation |
|---|---|
+ |
new JsBigInt(left.Value + right.Value) |
- |
new JsBigInt(left.Value - right.Value) |
* |
new JsBigInt(left.Value * right.Value) |
/ |
Division with zero check, truncates towards zero |
% |
Remainder with zero check |
** |
BigInteger.Pow(base, (int)exponent) |
- (unary) |
new JsBigInt(-operand.Value) |
// Division with zero check
public static JsBigInt operator /(JsBigInt left, JsBigInt right)
{
if (right.Value == BigInteger.Zero)
{
throw new DivideByZeroException("Division by zero");
}
return new JsBigInt(left.Value / right.Value);
}
// Power operation
public static JsBigInt Pow(JsBigInt baseValue, JsBigInt exponent)
{
if (exponent.Value < 0)
{
throw new ArgumentOutOfRangeException(nameof(exponent), "Exponent must be non-negative");
}
return new JsBigInt(BigInteger.Pow(baseValue.Value, (int)exponent.Value));
}Binary operations dispatch based on operand types:
File: Ast/TypedAstEvaluator.JsValue.cs
private static JsValue NumericBinaryOp(
in JsValue left,
in JsValue right,
Func<double, double, double> numericOp,
Func<JsBigInt, JsBigInt, JsBigInt> bigIntOp,
EvaluationContext context)
{
// Fast path: both are numbers
if (left.IsNumber && right.IsNumber)
{
return JsValue.FromDouble(numericOp(left.NumberValue, right.NumberValue));
}
// Convert to numeric (BigInt or Number)
var leftNumeric = ToNumericValue(left, context);
var rightNumeric = ToNumericValue(right, context);
// Both BigInt
if (leftNumeric.IsBigInt && rightNumeric.IsBigInt)
{
return bigIntOp(leftNumeric.AsBigInt(), rightNumeric.AsBigInt());
}
// Both Number
if (leftNumeric.IsNumber && rightNumeric.IsNumber)
{
return JsValue.FromDouble(numericOp(leftNumeric.NumberValue, rightNumeric.NumberValue));
}
// Mixed types - TypeError
throw ThrowTypeError("Cannot mix BigInt and other types", context);
}BigInt supports all bitwise operations except unsigned right shift (>>>):
File: JsTypes/JsBigInt.cs
| Operation | Implementation | Notes |
|---|---|---|
& (AND) |
left.Value & right.Value |
Works on infinite precision |
| (OR) |
left.Value | right.Value |
Works on infinite precision |
^ (XOR) |
left.Value ^ right.Value |
Works on infinite precision |
~ (NOT) |
~operand.Value |
Returns -(n + 1)
|
<< |
Left shift with sign handling | Negative shifts reverse direction |
>> |
Right shift with sign handling | Negative shifts reverse direction |
// Left shift with negative handling
public static JsBigInt operator <<(JsBigInt left, JsBigInt right)
{
var shiftAmount = (int)right.Value;
return shiftAmount < 0
? new JsBigInt(left.Value >> -shiftAmount)
: new JsBigInt(left.Value << shiftAmount);
}Per ECMAScript spec, >>> is not supported for BigInt:
File: Ast/TypedAstEvaluator.cs
private static JsValue UnsignedRightShiftJsValue(in JsValue left, in JsValue right, EvaluationContext context)
{
var leftNumeric = ToNumericValue(left, context);
if (leftNumeric.IsBigInt)
{
throw ThrowTypeError("Cannot use unsigned right shift on BigInt", context);
}
// ... Number handling
}File: StdLib/StandardLibrary.cs
flowchart TD
Input["ToBigInt(value)"]
Input --> Check{value.Kind?}
Check -->|undefined| TypeError1["TypeError"]
Check -->|null| TypeError2["TypeError"]
Check -->|boolean| Boolean["false → 0n\ntrue → 1n"]
Check -->|bigint| Return["return value"]
Check -->|number| NumberCheck{Is integer?}
Check -->|string| Parse["Parse as BigInt\n(hex/bin/octal)"]
Check -->|symbol| TypeError3["TypeError"]
Check -->|object| ToPrimitive["ToPrimitive(Number)\nthen recurse"]
NumberCheck -->|yes| Convert["Convert to BigInt"]
NumberCheck -->|no| RangeError["RangeError"]
internal static JsBigInt ToBigInt(JsValue value, EvaluationContext? context = null)
{
return value.Kind switch
{
JsValueKind.Undefined => throw ThrowTypeError("Cannot convert undefined to BigInt"),
JsValueKind.Null => throw ThrowTypeError("Cannot convert null to BigInt"),
JsValueKind.Boolean => value.IsTrue ? JsBigInt.One : JsBigInt.Zero,
JsValueKind.BigInt => value.AsBigInt(),
JsValueKind.Number => ConvertNumberToBigInt(value.NumberValue),
JsValueKind.String => ParseBigIntString((string)value.ObjectValue!),
JsValueKind.Symbol => throw ThrowTypeError("Cannot convert Symbol to BigInt"),
JsValueKind.Object => ToBigInt(ToPrimitive(value, "number"), context),
_ => throw new InvalidOperationException()
};
}BigInt can be compared with Numbers using relational operators:
File: Runtime/JsOps.cs
private static int? CompareBigIntToDouble(BigInteger bigInt, double number)
{
// Handle special cases
if (double.IsNaN(number)) return null;
if (double.IsPositiveInfinity(number)) return -1; // bigInt < +Infinity
if (double.IsNegativeInfinity(number)) return 1; // bigInt > -Infinity
// Compare mathematical values with precision handling
var bigIntDouble = (double)bigInt;
if (bigIntDouble < number) return -1;
if (bigIntDouble > number) return 1;
// For exact comparison when values are close
if (BigInteger.TryParse(number.ToString("R"), out var numberAsBigInt))
{
return bigInt.CompareTo(numberAsBigInt);
}
return 0;
}File: StdLib/BigInt/BigIntConstructor.cs
// Cannot be called with new
new BigInt(123); // TypeError
// Must be called as function
BigInt(123); // 123n
BigInt("456"); // 456n
BigInt(true); // 1n| Method | Description |
|---|---|
BigInt.asIntN(bits, value) |
Wraps value to signed N-bit integer |
BigInt.asUintN(bits, value) |
Wraps value to unsigned N-bit integer |
// asIntN - signed integer truncation
[JsHostMethod("asIntN", Length = 2d)]
public JsValue AsIntN(JsValue _, IReadOnlyList<JsValue> args)
{
var bits = (int)ToNumber(args.GetArgument(0));
var value = ToBigInt(args.GetArgument(1));
var mod = BigInteger.One << bits;
var result = value.Value % mod;
// Convert to signed if in upper half
if (result >= (BigInteger.One << (bits - 1)))
{
result -= mod;
}
return new JsBigInt(result);
}File: StdLib/BigInt/BigIntPrototype.cs
| Method | Description |
|---|---|
toString(radix) |
Converts to string with radix 2-36 |
valueOf() |
Returns primitive BigInt value |
toLocaleString() |
Locale-aware string conversion |
BigInt has dedicated typed arrays for 64-bit integers:
Files:
JsTypes/JsBigInt64Array.csJsTypes/JsBigUint64Array.csJsTypes/BigIntTypedArrayBase.cs
// BigInt64Array - signed 64-bit integers
public sealed class JsBigInt64Array : BigIntTypedArrayBase<long>
{
protected override int BytesPerElement => 8;
protected override JsValue GetElement(int index)
{
var value = BinaryPrimitives.ReadInt64LittleEndian(
Buffer.Data.AsSpan(ByteOffset + index * 8));
return new JsBigInt(value);
}
}
// BigUint64Array - unsigned 64-bit integers
public sealed class JsBigUint64Array : BigIntTypedArrayBase<ulong>
{
// Similar implementation with ulong
}All hot path methods use aggressive inlining:
[MethodImpl(JsEngineConstants.Inlining)]
public bool IsBigInt => Kind == JsValueKind.BigInt;Binary operations check for Number-only paths first:
// Fast path - most common case
if (left.IsNumber && right.IsNumber)
{
return JsValue.FromDouble(left.NumberValue + right.NumberValue);
}
// Slower path - handle BigInt and mixed typesPre-allocated constants reduce allocations:
public static JsBigInt Zero => new(BigInteger.Zero);
public static JsBigInt One => new(BigInteger.One);File: Ast/TypedAstEvaluator.ExecutionPlanRunner.Handlers.Operators.cs
else if (incCurrentValue.IsBigInt)
{
var bigInt = (JsBigInt)incCurrentValue.ObjectValue!;
incOldNumericValue = incCurrentValue;
var incNewBigInt = instruction.IsIncrement
? bigInt.Value + 1
: bigInt.Value - 1;
incNewJsValue = new JsBigInt(incNewBigInt);
}flowchart LR
subgraph Allowed["Allowed Operations"]
A1["1n + 2n = 3n"]
A2["1n < 2 = true"]
A3["1n == 1 = true"]
A4["1n === 1n = true"]
end
subgraph Forbidden["TypeError"]
F1["1n + 2"]
F2["1n * 2"]
F3["Math.sqrt(4n)"]
F4["1n >>> 0"]
end
| Operation | BigInt + BigInt | BigInt + Number | Result |
|---|---|---|---|
+, -, *, /, %
|
Allowed | TypeError | BigInt |
<, >, <=, >=
|
Allowed | Allowed | Boolean |
== |
Allowed | Allowed | Boolean |
=== |
Allowed | Always false | Boolean |
>>> |
TypeError | N/A | N/A |
| Category | File Path |
|---|---|
| Core Type | src/Asynkron.JsEngine/JsTypes/JsBigInt.cs |
| Value Storage | src/Asynkron.JsEngine/JsTypes/JsValue.cs |
| Kind Enum | src/Asynkron.JsEngine/JsTypes/JsValueKind.cs |
| Constructor | src/Asynkron.JsEngine/StdLib/BigInt/BigIntConstructor.cs |
| Prototype | src/Asynkron.JsEngine/StdLib/BigInt/BigIntPrototype.cs |
| Arithmetic | src/Asynkron.JsEngine/Ast/TypedAstEvaluator.JsValue.cs |
| Bitwise | src/Asynkron.JsEngine/Ast/TypedAstEvaluator.cs |
| Coercion | src/Asynkron.JsEngine/StdLib/StandardLibrary.cs |
| Comparison | src/Asynkron.JsEngine/Runtime/JsOps.cs |
| BigInt64Array | src/Asynkron.JsEngine/JsTypes/JsBigInt64Array.cs |
| Tests | tests/Asynkron.JsEngine.Tests/BigIntTests.cs |
- JsValue-System - How primitive types are stored
- Performance-Patterns - Optimization techniques
- Temporal-API - Uses BigInteger for nanosecond precision