This site describes solve-engine as it is on main: 2.43.0, which npm does not have yet. npm installs 2.40.0, so a page may show an answer that version does not give yet.
Symbolic
Built in. The arrow belongs to the engine rather than to a package, so plain arithmetic on unknowns works whichever packages are registered. Some examples on this page read a form a package supplies: a name the lexer also reads as a unit (
b) needsVARIABLES_PACKAGE, a function call such assqrt(x)needsFUNCTION_PACKAGE, and a matrix needsMATRIX_PACKAGE.createEngine()registers all three.
Symbolic arithmetic works with letters that have no value yet, the way algebra
on paper does: x + x is 2x whatever x turns out to be. Ending a line with
an arrow evaluates it in a mode where a name with no value
stays symbolic instead of becoming an error.
1+2+x+3+x => // 2x+6An unknown survives arithmetic, exponentiation, negation and function calls, so an expression keeps its shape rather than losing the terms that involve it.
x^2+3x+2 => // x^2+3x+2-x => // -xsqrt(x) => // sqrt(x)An unknown has no amount
Section titled “An unknown has no amount”Some operations need one amount to work on: giving a value a unit or a
currency (km, $), writing it as a percentage, in another base, as a
fraction or in scientific notation, and as number. An unknown has no amount,
so under the arrow those are refused with the same error the line gives
without it, naming the unknown. They used to read the unknown as zero, so
foo percent => answered 0.00% while foo percent said the name was
undefined.
foo + 1 => // foo+1foo percent => // ERROR: Undefined variable: foofoo km => // ERROR: Undefined variable: foofoo as hex => // ERROR: Undefined variable: fooOnce the name has a value, the same lines answer with it.
foo = 12 // 12foo percent => // 12.00%foo km => // 12.00 kmA formula stored by a bare assignment, y = x + 1, is refused the same way
when a later line gives it a unit with x still unknown.
Constants are values
Section titled “Constants are values”A constant is a number with a name, so it is never an unknown. pi, e,
tau and phi are read as their values under the arrow, and so are π
and ans, the line above’s answer: each line answers what it answers without
the arrow. A formula holds a constant as its decimal, the way it holds any
other number.
π km => // 3.14 kmpi + x => // x+3.14159265362 + 3 // 5ans km => // 5.00 kmAn equation that mentions π has one unknown fewer for it, so 2x = π is
stored and solved for x. A note that gives π or ans a value of its own
is read with that value, under the arrow too.
A formula keeps no units
Section titled “A formula keeps no units”A formula is algebra on numbers: it records how the unknowns combine, and it has nowhere to keep a unit beside them. Arithmetic between an unknown and an amount in a unit (a length, a weight, money) is therefore refused by name, saying which unit would be lost, rather than answered with the unit dropped. Once the unknown has a value, the line is ordinary arithmetic and keeps its unit.
foo * 5 km => // ERROR: A formula keeps no units, so combining "foo" with an amount in km would drop the km. Give "foo" a value on a line above, or write the formula without the unit.foo = 3 // 3foo * 5 km => // 15.00 kmThe same refusal covers a power, a function call and either side of solve,
so solve(2x = 4 km, x) is refused rather than answered 2. A plain number and
a percentage are not units and combine as before. Carrying units through a
formula is a feature of its own, and not one this page offers yet.
A percentage of an unknown
Section titled “A percentage of an unknown”A percentage is a share of something. Added to a number it is a share of that
number, so 200 + 10% adds a tenth of 200 and is 220. Added to an unknown it
is a share of the unknown in the same way: foo + 10% is 1.1foo, and once
foo is 200 the formula answers 220, as the line with the number does.
foo + 10% => // 1.1foofoo - 10% => // 0.9foo200 + 10% // 220y = x + 10% // 1.1xx = 200 // 200y // 220The boundary: a percentage written first is a percentage plus a number, which
the engine reads as a proportion (10% + 5 is 510%), so 10% + foo => stays
foo+0.1, the same value.
How a formula is written
Section titled “How a formula is written”A formula is shown in a form that reads back as itself: typed into a line, the
answer it shows is the formula you started from. A fraction that has no short
decimal goes after its term as a division, so one 1200th of a salary is
salary/1200 rather than 1/1200salary, which reads as one over 1200
salaries. The engine reads a leading minus as part of what follows, so
-x^2 is (-x)^2; the negative of a square is therefore written -(x^2).
Two minus signs that cancel are removed, and a quotient under a quotient is
turned over.
x*(1/3) => // x/3x/(1/y) => // x*y-x/-y => // x/y-(x^2) => // -(x^2)solve(salary/1200 * rate = net, rate) // 1200*net/salaryA coefficient is written beside its term (2x) only where the two read as a
product; otherwise a * separates them, as in 1200*net, since 1200n is a
whole number.
The same goes for a name that is also a unit or a size word. A number written
straight before a unit is an amount of it, so 2b is two bits and 2m two
metres, and a number before k or million scales it, so 2k is two
thousand. An unknown called b, m or k is therefore written after a *
(2*b), and typed back that is the formula again: a unit word with no number
of its own in front of it is read as a name.
1+2+b+3+b => // 2*b+6m + m => // 2*mk * 3 => // 3*k2*b => // 2*b2b // 2.00 bA slash before a unit means “per”, so 0.5/m is half of something per metre.
A division by an unknown called m is therefore written with the name in
brackets, which reads back as the division:
0.5/m // 0.50 /m0.5/(m) => // 0.5/(m)solve(b*m = 4, m) // 4/(b)The boundary: only the units and size words built into the engine are known to the printer. A unit a note defines for itself is not, so an unknown with that unit’s name is still written beside its coefficient. A fraction whose parts are over a million is shown as its decimal, to ten significant figures.
Exact arithmetic
Section titled “Exact arithmetic”Coefficients are exact rationals rather than floating-point numbers, so a value reads back as it was written and a fraction stays a fraction.
x/3 => // x/32^10 + x => // x+1024This matters most where rounding would be indistinguishable from a real result:
a matrix entry that is structurally zero can arrive as a value like
0.0000000000000000555 in floating point, which is enough to make a singular
matrix look invertible.
A function of a number folds to its value: sqrt(4) becomes 2 and sqrt(2)
its decimal 1.41. A function of an unknown is left as written, so sqrt(x)
stays sqrt(x) rather than inventing a value for the unknown.
An expression that divides by zero has no value, whatever its unknowns are, so it is refused where it is written rather than carried into the algebra. Expanding it, differentiating it or solving an equation over it would otherwise work on a quantity that does not exist. Only an exact zero counts: a very small number is an ordinary divisor.
expand((x+1)/0) // This expression divides by zero, so it has no value, whatever its unknowns are.der(x/0, x) // This expression divides by zero, so it has no value, whatever its unknowns are.expand((x*0)^-1) // This expression divides by zero, so it has no value, whatever its unknowns are.The bounded simplifier
Section titled “The bounded simplifier”Simplification is deliberately limited. It folds constants, applies additive and
multiplicative identities, collects like terms in a top-level sum, cancels two
minus signs, and turns a quotient under a quotient over (x/(1/y) is x*y). It does
not apply trigonometric identities, and it never expands or factors on its own,
which is what keeps x^2 from turning back into x*x.
Solving a linear system
Section titled “Solving a linear system”Writing a product chain equal to a value stores an equation. Asking for the unknown solves it.
a = [1, 2; 3, 4]a*x = [60; 70]x => // [-50.00; 55.00]This also works when the coefficients are themselves unknown, which is what makes it useful for deriving a formula rather than only a number: a matrix whose cells are unknowns solves to a matrix of formulas.
A factor that has no value at all leaves nothing to multiply out, so asking
for the unknown names the factor and the other way to ask: solve, which
treats the factor as an unknown and gives the formula.
a*x = b // x stored as an equation: solve with "x =>"x => // ERROR: Cannot solve for "x": "a" is not yet defined. Give "a" a value on a line above, or solve for "x" in terms of it with solve(a*x = b, x).solve(a*x = b, x) // b/a