Numeric Evaluation
To obtain an exact numeric evaluation of an expression use expr.evaluate().
To obtain a numeric approximation use expr.N().
Parse + Numeric Free Function
For the common "parse then numeric approximation" flow, use the N() free
function:
The N() free function accepts either a LaTeX string or an Expression.
It uses a shared ComputeEngine instance created on first call.
Exact Evaluation
An evaluation with expr.evaluate() preserves exact values.
Exact values are:
| Type | Examples |
|---|---|
| Integers | 42, -1234 |
| Rationals | \nicefrac{3}{4}, -\nicefrac{11}{7} |
| Square Roots of Integers | \sqrt{2}, -3\sqrt{5} |
| Constants | e (ExponentialE), \pi (Pi) |
A number literal is exact or a float by the way it is written. A literal with
no fraction part and no negative exponent is an exact integer: 1e3 is the
exact 1000. A literal with a fraction part is a float, also when the fraction
is zero (1.0, 1.5e3), and so is a literal with a negative exponent
(1e-3).
Numeric Approximation
To force the evaluation of an expression to be a numeric approximation, use expr.N().
When using expr.evaluate(), if one of the arguments is not an exact value
the expression is automatically evaluated as a numeric approximation.
An integer function of an exact integer argument gives the exact integer
under expr.N() too, because an approximation of the argument would change
the answer: DigitSum, Binomial, Factorial, Fibonacci, Mod, GCD,
LCM and the integer predicates (IsPrime, IsEven, …) read an exact
operand as written. Only an inexact (float) operand is read as a float.
ce.box(['DigitSum', ['Power', 2, 1000]]).N();
// ➔ 1366 (the same as evaluate())
ce.box(['GCD', ['Power', 2, 100], ['Power', 6, 50]]).N();
// ➔ 1125899906842624 (2^50)
ce.box(['IsPrime', ['Subtract', ['Power', 2, 89], 1]]).N();
// ➔ True
Infinity and NaN
The two non-finite results fall on opposite sides of the exactness rule above,
which is why they behave differently under evaluate().
\pm\infty is an exact value. A function of an infinity is therefore
treated like a function of any other exact argument: if there is a closed form
it is used, and if there is not, the expression stays symbolic rather than
numericizing.
There is no inexact infinity. At machine precision, a float that is too large
for a double, such as 1.0e800, becomes +\infty, and that infinity is exact
(isExact is true) like any other. At arbitrary precision the same literal
stays a finite number.
NaN is not an exact value. It is the argument that triggers the automatic
numeric approximation, so any numeric function of NaN evaluates to NaN —
under plain evaluate(), not only under N(). There is no exact value to hold
on to:
Note that this is a statement about values, not about types. In the type
system the bare numeric types — integer, rational, real, complex —
denote finite values only, so neither an infinity nor NaN inhabits them.
Both are still number.
Angular Units
When a trigonometric function is given a unitless value, the Compute Engine
interprets it using the current angular unit. Set it with ce.angularUnit
(default: "rad").
Valid values: "rad", "deg", "grad", "turn".
The angular unit applies to the trigonometric functions (Sin…Csc,
Haversine), whose argument is an angle, and to the inverse trigonometric
functions (Arcsin…Arccsc, Arctan2, InverseHaversine), whose result is
an angle — in degree mode \arcsin(1) evaluates to the exact value 90.
Hyperbolic functions and their inverses are not affected: their argument
is a dimensionless number, not an angle.
Compiled functions honor the angular unit as well: with
ce.angularUnit = "deg", ce.compile("\\sin(x)") returns a function that
interprets x in degrees, matching evaluate().
The symbolic integrator follows the angular unit as well. With
ce.angularUnit = "deg", \int_0^{180} \sin x\,dx evaluates to 360/\pi
and \int \sin x\,dx to -(180/\pi)\cos x. An integral with no
trigonometric function, such as \int_0^1 \frac{dx}{1 + x^2} = \pi/4, has the
same value in every unit. The same holds with the integration rules library
loaded, and for the closed form the JavaScript target compiles for a definite
integral.
Rounding Ties
Round rounds a number to the nearest integer. A value exactly halfway
between two integers (a tie) is rounded with the rule of ce.roundingTies
(default: "away-from-zero").
| Rule | Round(2.5) | Round(3.5) | Round(-2.5) | Used by |
|---|---|---|---|---|
"away-from-zero" | 3 | 4 | -3 | the default |
"to-even" | 2 | 4 | -2 | IEEE 754, Python, NumPy, Mathematica |
"toward-zero" | 2 | 3 | -2 | |
"toward-positive-infinity" | 3 | 4 | -2 | JavaScript Math.round |
"toward-negative-infinity" | 2 | 3 | -3 |
The rule applies at every precision, to an exact rational (Round(5/2)), and
to the step form Round(x, step), which rounds to the nearest multiple of the
step: with "to-even", Round(0.125, 1/100) is 3/25. A value that is not a tie is not
affected.
Compiled functions honor the rule as well: the code is made with the rule in effect when the expression is compiled, and a function compiled before a change keeps its rule.
Remainder does not use this rule: its quotient is always rounded with a tie
toward +∞.
JavaScript Interoperability
The result of expr.evaluate() and expr.N() is a expression.
The numericValue property of this expression is either a machine number
(a JavaScript number), a NumericValue object or null if the expression
is not a number.
While a NumericValue object can represent arbitrary precision numbers, for
use with JavaScript, a reduced precision approximation can be accessed using
the re (for the real part of the number) and im (for the imaginary part)
properties.
const expr = parse('1/3 + 1/4');
console.log(expr.N().re);
// ➔ 0.5833333333333334
Another way to obtain a JavaScript compatible representation of an expression
is to use the valueOf() method of the expression.
const expr = parse('1/3 + 1/4');
console.log(expr.N().valueOf());
// ➔ 0.5833333333333334
The valueOf() method of a expression can be used in JavaScript
expressions.
Unlike the .re property, valueOf() can also return a boolean or a
string, depending on the value of the expression.
To get an Expression number literal from a JavaScript number, use ce.expr() or ce.number().
Numeric Precision
The number of significant digits used in numeric calculations is controlled
by the ce.precision property of the ComputeEngine instance.
The default precision is 21.
To set the precision to the default value, use ce.precision = "auto".
To request a specific precision for a single evaluation, use the N
function with a precision argument: ["N", expr, digits].
The value is computed with the requested digits plus a few guard digits, then
rounded to the requested digits. The working precision of the engine
(ce.precision) does not change: after ce.box(['N', 'Pi', 30]).evaluate(),
ce.precision is still 21. A later operation on the result is computed at the
working precision, so ["Add", ["N", "Pi", 30], 1] evaluates to
4.14159265358979323846. The reference page for
N describes the precision and accuracy
goals ["N", expr, ["List", p, a]].
Machine Precision
If the precision is 15 or less, the Compute Engine uses a 64-bit binary floating point format for its internal calculations.
This format is implemented in hardware and well suited to do fast computations.
It uses a fixed amount of memory and represents significant digits in base-2 with
about 15 digits of precision and with a minimum value of \pm5\times 10^{-324} and a maximum value of \pm1.7976931348623157\times 10^{+308}
To change the precision to machine precision, use
ce.precision = "machine".
With this precision, some calculations that have a value very close to 0
may return 0 and some calculations that have a value greater than the
maximum value representable by a machine number may return \pm\infty.
Some numeric evaluations using machine numbers cannot produce exact results.
ce.precision = 'machine';
console.log(ce.parse('0.1 + 0.2').N().json);
// ➔ "0.30000000000000004"
While 0.1 and 0.2 look like "round numbers" in base-10, they can
only be represented by an approximation in base-2, which introduces cascading
errors when manipulating them.
Arbitrary Precision
If ce.precision is greater than 15, numbers are represented as bignum numbers,
a string of base-10 digits and an exponent.
Bignum numbers have a minimum value of \pm 10^{-9\,000\,000\,000\,000\,000} and a maximum value of \pm9.99999\ldots \times 10^{+9\,000\,000\,000\,000\,000}.
ce.precision = 21;
console.log(ce.parse('0.1 + 0.2').N().json);
// ➔ "0.3"
Trigonometric operations are accurate for precision up to 1,000.
Serialization and Precision
The precision property controls how computations are performed. It also
affects how results are displayed:
.latexand.toString()round numeric output toce.precisionsignificant digits. This hides noise digits that arise from internal rounding in division and transcendental functions..jsonandtoJSON()emit the full unrounded value for lossless data interchange. Useexpr.toMathJson({ digits: 'auto' })to get precision-rounded MathJSON output.
To explicitly control digits in MathJSON output, use
expr.toMathJson() with the digits option:
expr.toMathJson({ digits: 'auto' }); // rounded to ce.precision
expr.toMathJson({ digits: 'max' }); // all digits (default)
expr.toMathJson({ digits: { fractional: 5 } }); // 5 fractional digits
expr.toMathJson({ digits: { significant: 5 } }); // 5 significant digits
A float keeps its digits in MathJSON, also when they repeat: the float
1.0/3 serializes as "0.333333333333333333333". A number string with a
repetend in parentheses ("0.(3)") is an exact rational, so writing a float
that way would make ce.box() read it back as the exact 1/3. To mark the
repeating digits of a float, use expr.toMathJson({ repeatingDecimal: true }).
When the precision is greater than 15, the return value of expr.N().json may
be a MathJSON number with a num string containing all available digits:
{
"num": "3.14159265358979323846264338327950288419716939937510"
}
The .json output may contain more digits than ce.precision because
some arithmetic operations (addition, subtraction, multiplication) are exact
and preserve all digits. Digits beyond the working precision are not
guaranteed to be accurate. For display purposes, use .latex or
.toString() which automatically round to the working precision.
Repeated Evaluation
To repeatedly evaluate an expression use ce.assign() to change the value
of variables. ce.assign() changes the value associated with one or more
variables in the current scope.
ce.assign() accepts booleans, numbers, bigints, expressions, or
functions. Use undefined to clear a value.
You can also use expr.subs(), but this will create a brand new expression on
each iteration, and will be much slower.
To reset a variable to be unbound to a value use ce.assign()
To change the value of a variable set its value property:
ce.symbol("x").value = 5;
ce.symbol("x").value = undefined;
Compiling
If performance is important, the expression can be compiled to a JavaScript function.
To get a compiled version of an expression use the compile() function:
import { compile } from '@cortex-js/compute-engine';
const result = compile("3x^2+4x+2");
for (const x = 0; x < 1; x += 0.01) console.log(result.run({ x }));
The syntax {x} is a shortcut for {"x": x}, in other words it defines an
argument named "x" (which is used in the definition of the expression expr)
with the value of the JavaScript variable x (which is used in the for loop).
This will usually result in a much faster evaluation than using expr.N() but
this approach has some limitations.
Simplifying Before Evaluating
When using expr.N(), no rewriting of the expression is done before it is
evaluated.
Because of the limitations of machine numbers, this may produce surprising results.
For example:
ce.precision = "machine";
const x = ce.parse("0.1 + 0.2").N();
console.log(ce.expr(["Subtract", x, x]).N());
// ➔ 2.7755575615628914e-17
However, the result of x - x from ce.simplify() is 0 since the
simplification is done symbolically, before any floating point calculations are
made.
const x = ce.parse('0.1 + 0.2').N();
console.log(ce.parse('x - x').simplify());
// ➔ 0
In some cases, it may be advantageous to invoke expr.simplify() before using
expr.N().
Tolerance
Two numbers that are sufficiently close to each other are considered equal.
To control how close two numbers have to be before they are considered
equal, set the tolerance property of a ComputeEngine instance.
By default, the tolerance is 10^{-10}.
The tolerance is accounted for by the Chop function to determine when to
replace a number of a small magnitude with the exact integer 0.
It is also used when doing some comparison to zero: a number whose absolute value is smaller than the tolerance will be considered equal to 0.
Two numbers whose difference is smaller than the tolerance are considered
equal. This applies only when a floating point number is involved: two exact
numbers (integers, rationals, or rational multiples of a square root) are
compared exactly, so \frac{1}{3} and \frac{3333333333333}{10^{13}} are not
equal, while \frac{1}{10} and 0.1 are.
The tolerance is an absolute value, and it does not change with the precision:
at 40 digits, two values that differ by 10^{-12} still compare as equal. A
tolerance you set is kept when the precision changes.
To make the tolerance follow the precision, set it to "auto". The
tolerance is then 10^{-(\text{precision} - 2)}, two digits short of the
working precision, and it is recomputed at each precision change: 10^{-19} at
the default 21 digits, 10^{-38} at 40 digits. This mode is meant for
big-decimal work: a value computed in machine floats (about 16 digits) then
compares as different from its exact value.
const ce = new ComputeEngine({ precision: 40, tolerance: "auto" });
ce.tolerance; // 1e-38
ce.precision = 50;
ce.tolerance; // 1e-48
Numeric Functions
The topics below from the MathJSON Standard Library can provide numeric evaluations for their numeric functions:
| Topic | Symbols/Functions |
|---|---|
| Arithmetic | Add Multiply Power Exp Log ExponentialE ImaginaryUnit... |
| Calculus | Derivative Integrate... |
| Complex | Real Conjugate, ComplexRoots... |
| Special Functions | Gamma Factorial... |
| Statistics | StandardDeviation Mean Erf... |
| Trigonometry | Pi Cos Sin Tan... |