Math Engine
Evaluation Pipeline
Input processing follows a three-stage pipeline:
bytes ──► lexer ──► tokens ──► compiler ──► bytecode ──► vm ──► result
-
Lexer (
lexer::tokenise_expression) — character-by-character state machine, emits up to 64Tokenvalues into a flat[Token; 64]array. Numbers are converted to Q31.32 immediately. Unary minus detection uses context-sensitive lookback. Mode-gated tokens:i→ imaginary unit only in Advanced mode;[/]→ matrix brackets only in Matrix mode;E→ exponent suffix only in Scientific mode. -
Compiler (
compiler::compile) — recursive-descent precedence climbing (same grammar as the original parser) emits opcodes into a 256 BBytecodebuffer. No AST is ever built in memory. 12 opcodes cover all operations, functions, and control flow. The bytecode is a linear program ending inHalt. -
VM (
vm::execute) — flatloop { match op { ... } }dispatch with a 16-entryOption<Matrix>value stack on the C stack. Zero C recursion — all operand storage is in the explicit value stack. Overflow tracked via a localoverflow: Option<(i64, bool)>variable rather than bubbling through call frames.
The private API exposed to the runtime consists of two entry points:
engine::evaluate_expression() and engine::format_result().
Q31.32 Contract
All numeric values are stored as signed 64-bit integers under the Q31.32 fixed-point convention, with the binary point positioned between bits 31 and 32:
The constant FIXED_ONE equals , giving a
precision of , or approximately nine
decimal digits. The representable range is approximately
billion in real terms.
Multiplication promotes to i128:
Symmetric rounding (half away from zero) is applied via absolute-value arithmetic before the final truncation.
Division computes:
Both return None on overflow or division by zero. Addition and subtraction
use saturating arithmetic (.saturating_add, .saturating_sub) so overflow
clamps to i64::MIN or i64::MAX instead of wrapping.
Error model: EvalResult
The evaluator returns EvalResult, a tri-state enum:
enum EvalResult {
Matrix(Matrix),
Overflow { mantissa: i64, exponent: i32, negative: bool },
DomainError,
}
Matrix— a normal result. The unifiedMatrixtype can represent a scalar (1×1), complex number (1×2), matrix (up to 4×4), or scientific notation value (1×2 withkind = Scientific). Results are formatted to 6 decimal places with trailing zeros stripped.Overflow— the result exceeds Q31.32 range or the scientific notation display limit (|exponent| > 99). The overflow subsystem stores a log10 estimate and sign via a thread-local static, then computes a scientific-notation display at the top-level return.DomainError— an invalid input (e.g.sqrt(-1)in Standard mode), rendered as! error.
Constants from fixed_point.rs (exact Q31.32 values):
| Constant | Decimal value | Q31.32 value |
|---|---|---|
FIXED_PI | 13,493,037,705 | |
FIXED_E | 11,674,931,555 | |
FIXED_LN2 | 2,977,044,472 | |
FIXED_LN10 | 9,889,527,671 | |
FIXED_SQRT2 | 6,074,001,000 | |
FIXED_PI_OVER_180 | 74,961,321 | |
FIXED_180_OVER_PI | 246,083,499,208 | |
CORDIC_GAIN | 2,608,131,496 |
Opcodes
12 opcodes encoded in a single byte, some followed by operand bytes:
| Opcode | Byte | Followed by | Stack effect |
|---|---|---|---|
PushI64 | 0x01 | 8 bytes LE i64 | → val |
PushReg | 0x02 | 1 byte register | → val |
PushAns | 0x03 | — | → ans |
PushConst* | 0x04–0x06 | — | → π/e/i |
PushMatReg | 0x07 | 1 byte register | → matrix |
PushMatLit | 0x08 | 1 byte cache index | → matrix |
ConstructSci | 0x09 | — | → Scientific(mant, exp) |
Add/Sub/Mul/Div/Mod/Pow/Neg | 0x10–0x16 | — | pop 2 → push 1 |
CallFunction | 0x20 | 1 byte function index | pop 1 → push 1 |
CallBinomP/PoissonP/ChiCDF/NthRoot | 0x40–0x43 | — | pop 2–3 → push 1 |
CallMatrixFunc | 0x50 | 1 byte function index | pop 1 → push 1 |
Sto/StoMat | 0x60–0x61 | 1 byte register | pop 1 → push 1 |
LoopSum/LoopInt | 0x70–0x71 | 5 bytes header | pop 2 → push 1 |
Halt | 0xFF | — | — |
CallFunction replaces 22 individual opcodes (CallSin through CallLnGamma)
by encoding the function index as a second byte. CallMatrixFunc similarly
replaces 6 individual opcodes (CallDet through CallAdjugate).
CORDIC — Sine and Cosine
CORDIC (COordinate Rotation DIgital Computer) computes and simultaneously using only shifts, adds, and a small lookup table.
Algorithm
The rotation-mode CORDIC iteratively rotates a vector toward the target angle . Starting from:
where is the CORDIC gain pre-multiplied into so the result is gain-compensated. At each iteration :
After 22 iterations the residual angle is radians.
Taylor Correction
To eliminate the residual error, a first-order Taylor correction is applied:
In CORDIC coordinates :
This reduces worst-case error from to .
Quadrant Folding
Angles outside are folded using reflection identities.
The function reduce_angle_to_principal normalises any angle to
using angle % TWO_PI.
Arctan Table
22 entries for (176 bytes), each entry in Q31.32. Values range from 3,373,259,426 () down to 2,048 ().
Arctangent — Rational Minimax
The arctangent uses a rational minimax approximation (Ganssle-Homer form) rather than CORDIC vectoring mode, saving cycles. Max error radians.
For :
For , the identity is used. Coefficients are least-squares fit on Chebyshev nodes, Q31.32 quantised.
Uses the smaller of or to avoid divide overflow, then corrects into the proper quadrant based on signs of and .
and
Domain: . Returns None otherwise.
Exponential —
Range reduction to , , then
degree-7 minimax polynomial via Horner. Max error .
Returns None for overflow (), Some(0) for underflow
().
Natural Logarithm —
Range reduction to with ,
then degree-10 minimax polynomial .
Max error . Returns None for .
and
Square Root —
CLZ-based initial guess via 32-entry LUT ( error), 3 Newton iterations on , then 1 final Newton step on . Max relative error .
Integer Power
Binary exponentiation (exponentiation by squaring), multiplies. Negative exponents compute .
Nth Root
Newton iteration for integer , delegates to sqrt for , to
for non-integer .
Hyperbolic Functions
All identity-based: , , . saturates at to .
Inverse hyperbolic functions use standard logarithmic forms.
Complex Arithmetic
Complex multiplication uses the direct formula. Complex division uses
Smith's overflow-safe formula — dividing through by the larger component
( or ) avoids the intermediate overflow of the naive formula.
All complex transcendentals use analytic continuations (see the source for
the full formula table in complex.rs).
Scientific Notation (Scientific mode)
Scientific notation stores values as a pair (mantissa, exponent) where the
mantissa is a Q31.32 value normalised to and the exponent is an
integer in .
Literal syntax
1.5E+10 → mantissa = 1.5·SCALE, exponent = +10
2E-5 → mantissa = 2.0·SCALE, exponent = -5
9.99E+99 → mantissa = 9.99·SCALE, exponent = +99
Fractional exponents (e.g. 2E1.2) are rejected as lex errors — the
exponent must be an integer.
Arithmetic rules
| Operation | Formula | Renormalisation |
|---|---|---|