Associates a C++ variable with a symbolic name.
- Signature:
void bind(T& var, const std::string& name, ...); - Supported Types:
double,float,int16_t,int32_t,int64_t. - Behavior:
- Bound variables must outlive the
mexce::evaluatorinstance. - Rebinding a name affects subsequent compilations. An existing compiled
expression retains its captured binding; unbinding that name still
resets the expression to
"0"when it uses the name. - Throws
std::logic_errorifnamecollides with a built-in function or constant.
- Bound variables must outlive the
Removes one or all variable bindings.
- Signature:
void unbind(const std::string& name, ...);,void unbind_all(); - Behavior:
- If a variable used by the currently compiled expression is unbound, the expression is safely reset to the constant
"0". - Throws
std::logic_errorifnameis unknown or empty.
- If a variable used by the currently compiled expression is unbound, the expression is safely reset to the constant
Compiles an expression, making it the default for evaluate().
- Signature:
void set_expression(std::string expr); - Behavior:
- Throws
mexce_parsing_exceptionon syntax errors, providing the position of the error. - Throws
std::logic_errorif the expression string is empty.
- Throws
Executes the expression most recently compiled by set_expression().
- Signature:
double evaluate();
Compiles and executes an expression for a single use without replacing the default expression.
It uses the current named bindings and default evaluator options; instance option
changes apply to set_expression(). To evaluate with configured options, call
set_expression() followed by evaluate().
- Signature:
double evaluate(const std::string& expression);
Returns a mutable reference to the evaluator's options. Changes take effect on the next set_expression() call.
- Signature:
options& opts(); - Example:
eval.opts().fast_math = true;
Get or set the evaluator's options.
- Signatures:
const options& get_options() const;,void set_options(const options& opts);
Options can be configured before calling set_expression() to control code generation behavior:
mexce::evaluator eval;
eval.enable_fast_math(); // Allow additional special-value assumptions
eval.use_x87_backend(); // Use x87 FPU instead of SSE2
eval.enable_cse(); // Enable common subexpression elimination
eval.set_expression("x + y"); // Options take effect here| Method | Description |
|---|---|
enable_fast_math() |
Permits removing complete cancellations and zero identities, including x-x → 0, x/x → 1, 0*x → 0, and 0/x → 0. Removed terms no longer propagate their NaN, infinity, or signed-zero values. |
use_x87_backend() |
Selects the x87 FPU backend on x86, with 80-bit internal precision. On Apple Silicon this preference is accepted and the ARM64 backend is used. |
enable_cse() |
Enables Common Subexpression Elimination. Repeated identical subexpressions are computed once and reused. Supported by the x87 and ARM64 backends. |
Both modes use algebraic simplification to reduce arithmetic work. In arithmetic
chains that can be regrouped, default mode combines like terms with a nonzero
net coefficient or exponent: x+x+x-x-x and x*x/x can become x. Complete
cancellations remain grouped as t-t or t/t, retaining both operands and
applying their results after the ordinary reduced chain. This avoids rounding
other terms through the cancelled value. The grouped subtraction returns NaN for an evaluated t that is NaN or
infinite; the grouped division also returns NaN when t is zero. Default mode
retains an explicit additive constant even when its combined value is zero.
These rules do not preserve every source intermediate, domain restriction, or
signed-zero result. For example, x*x/x at x = 0 can produce zero, and
x*x/(x*x) can produce one for a finite x whose square would overflow.
fast_math additionally permits removing the grouped cancellations and the
zero identities listed above. Either mode can be more accurate for a particular
expression; fewer operations can improve rounding, but neither mode has a
universal accuracy advantage. Compare both accuracy and speed for your inputs.
Finite additive constants whose combined value overflows, and finite nonzero
multiplicative constants whose coefficient loses range or subnormal precision,
can remain separate so other terms can balance their range. A destructive
constant-only fold is also deferred within an enclosing chain of the same
arithmetic family (+/- or *//). These guards do not extend to arbitrary
function or power folds, or guarantee optimal evaluation order or correct
rounding for every input.
On Apple Silicon, x86 backend preferences in options are compatibility
no-ops; get_backend() reports backend_type::arm64. Fast-math and CSE options
retain their meaning. ARM64 evaluates in double precision; it does not emulate
x87 extended precision.
mexce supports standard mathematical notation.
- Literals: Numbers in decimal (
123.45) or scientific (1.2345e+02) notation. - Operators: Infix operators with the following precedence:
Precedence Operator Function Description 1 (highest) ^,**powPower / Exponentiation 2 *,/mul,divMultiplication, Division 3 +,-add,subAddition, Subtraction 4 (lowest) <,>- Less-than and greater-than comparison - Unary Operators: Unary
+and-are supported. Note that power operators (^and**) bind tighter than unary minus, so-a**2is evaluated as-(a**2), matching Python semantics. Use parentheses to change the grouping:(-a)**2. - Comparison: The
<and>operators return adouble(1.0if true,0.0if false).
Source unary minus participates in algebraic simplification as subtraction
from zero. In default mode, -0.0 therefore produces positive zero. The
explicit neg(x) function directly changes the sign, so neg(0.0) produces
negative zero.
pi: The mathematical constant π.e: Euler's number e.
| Function | Description |
|---|---|
add(a,b), sub(a,b), mul(a,b), div(a,b) |
Basic arithmetic. |
neg(x) |
Negation (unary minus). |
abs(x) |
Absolute value. |
mod(a,b) |
Modulo operator. |
min(a,b), max(a,b) |
Minimum and maximum. |
sin(x), cos(x), tan(x) |
Trigonometric functions. |
pow(base, exp) |
General exponentiation. |
exp(x) |
Base-e exponent (e^x). |
sqrt(x) |
Square root. |
ln(x) / log(x) |
Natural logarithm. |
log2(x), log10(x) |
Base-2 and Base-10 logarithms. |
logb(base, value) |
Logarithm with a custom base. |
ylog2(y, x) |
Computes y * log2(x). |
ceil(x), floor(x), round(x), trunc(x), int(x) |
Rounding functions. |
sign(x) |
Returns -1.0 for negative x, 0.0 for zero and 1.0 for positive x. |
signp(x) |
Returns 1.0 for positive x, 0.0 otherwise. |
bnd(x, period) |
Wraps finite x to [0, period) for a finite positive period. |
bias(x, a), gain(x, a) |
Common tone-mapping curves (for inputs in [0,1]). |
expn(x) |
Returns the exponent part of x. |
sfc(x) |
Returns the significand (fractional part) of x. |
Constant power exponents +0.5 and -0.5 use sqrt(x) and 1/sqrt(x)
respectively. These shortcuts preserve the sign of zero: an input of -0.0
produces -0.0 and negative infinity respectively. A runtime-bound exponent
uses the general power operation and can differ from these shortcuts.
Nested powers deliberately follow an algebraic convention in both modes:
(x^b)^n can become x^(b*n) when both exponents are constants, n is an
integer, b is +0.5, -0.5 or an integer with magnitude at most 65,536, and
the combined exponent is finite. For x = -4, (x^0.5)^2 therefore returns
-4, while sqrt(x)^2 produces NaN. General fractional inner exponents such
as 1.5 do not use this rewrite.
mexce selects a code generation backend for the target architecture:
Intermediate precision and platform math libraries can produce different floating-point results. The library does not promise bit-for-bit equality across architectures or backends.
- Emits native AArch64 scalar instructions for double-precision arithmetic
- Calls C standard library math functions using the macOS ARM64 ABI
- Supports common subexpression elimination and fast-math optimizations
- Uses macOS JIT memory write protection and instruction-cache invalidation
- Reports
backend_type::arm64throughget_backend()
Evaluation uses 16 bytes of stack per simultaneously live operand, plus the function frame and math-library calls. Clear expressions have no fixed operand-depth limit, so applications must provide enough thread stack for the expressions they evaluate. Protected programs have a validated operand-depth limit of 1,024; that limit does not account for the caller's existing stack use.
- Uses SSE2 scalar instructions (
addsd,mulsd, etc.) for basic arithmetic - Uses SSE4.1
roundsdinstruction for rounding functions (floor,ceil,round,trunc) - Calls C standard library math functions for transcendentals (
sin,cos,exp,log, etc.) - Faster on modern CPUs due to better pipelining and avoiding x87 state transitions
- Results in XMM0 register (standard x64 ABI return convention)
Rounding on this backend requires SSE4.1; there is no automatic CPU-feature
check. On CPUs without SSE4.1, select use_x87_backend() before
set_expression() and execute with evaluate().
- Uses x87 FPU instructions with 80-bit internal precision
- Uses native x87 arithmetic, including periodic wrapping, and transcendental instructions
- Stack-based architecture can be more compact for certain expression patterns
- Supports Common Subexpression Elimination (CSE)
- On x86-64, enable with
eval.use_x87_backend();
On x86, compilation is limited by simultaneously live intermediate values.
SSE2 uses six operand registers on Windows and eight on Linux/macOS, falling
back to x87 when needed. x87 has eight stack registers; compilation throws
std::overflow_error when the modeled peak depth after algebraic simplification
exceeds that capacity.
This static check provides partial protection. It counts the modeled temporary
registers used by inline x87 built-ins and fused operations, but not stack use
inside C-library math routines. A runtime-library implementation may use x87
on either 32-bit or 64-bit x86 while the expression's values remain on the x87
stack. An accepted expression can therefore still overflow the stack at run
time instead of throwing std::overflow_error; passing the check is not a
complete overflow guarantee. Passing tests establish the exercised cases and
implementations, not every possible library implementation.