epher user guide
Welcome! epher is a programmable, scriptable calculator. You can use it for a quick calculation, or build up your own functions and small programs. Everything is available in eight languages.
This guide is for complete beginners. It starts with the simplest possible calculation and builds up to the full power of the language. Every example shows what you type and what epher answers.
There are five ways to use epher. Pick whichever suits you:
| Version | What it is | Best when |
|---|---|---|
| Command line (CLI) | Text commands in a terminal | You live in a terminal and like scripts |
| REPL | An interactive epher session at the epher> prompt |
You want quick back-and-forth without leaving the terminal |
| Terminal UI (TUI) | A full-screen program inside the terminal | You want a terminal app with graphs and history on screen |
| Desktop app | A normal desktop program with its own window | You want a regular application |
| Web app (PWA) | Runs in your browser, installable, works offline | You want the fastest start; no installation |
The desktop app, the command line, the REPL, and the terminal UI are one
program: a single download installs the epher command, which does all
four. The web app is the exception: it needs no download at all.
All five versions understand exactly the same language. Learn it once, use it anywhere.
1. The epher language
This chapter teaches the language shared by every version of epher. In the web
app or desktop app, type an expression and press Enter (or click the
= button). In the CLI, start the session with epher repl and type after
the epher> prompt. In the TUI (epher tui), just type and press Enter. In the CLI you can also write
epher "expression" to evaluate one expression directly.
1.1 Your first calculation
Type this:
2 + 3 * 4
epher answers:
14
Multiplication is done before addition, exactly like in mathematics. That rule is called operator precedence.
1.2 Order of operations
The full precedence order, from strongest to weakest:
!factorial and%percent (both postfix)^power*and/multiplication and division+and-addition and subtraction
Use parentheses to change the order:
(2 + 3) * 4
20
The ^ operator computes powers, and it works right-to-left:
2 ^ 10
1024
2 ^ 3 ^ 2
512
(2 ^ 3 ^ 2 means 2 ^ (3 ^ 2), which is 2 ^ 9 = 512.)
Powers can be fractional. 2 ^ 0.5 is the square root of 2:
2 ^ 0.5
1.41421356237
Subtraction and division work left-to-right:
10 - 3 - 2
5
The % sign is a postfix operator that means "divided by 100": 5% is
0.05. It never looks at the operators around it, so 200 + 10% is
200.1. To increase 200 by 10%, spell the multiplication:
200 * (1 + 10%)
220
1.3 The special numbers pi, e, tau and phi
The famous constants are built in:
pi
3.14159265359
2 * pi
6.28318530718
e
2.71828182846
Two more: tau is a full turn (2 pi), and phi is the golden ratio:
tau
6.28318530718
phi
1.61803398875
1.4 Comparing and logic
You can compare numbers. The result is either true or false:
| Comparison | Meaning |
|---|---|
a > b |
a is greater than b |
a < b |
a is less than b |
a >= b |
a is greater than or equal to b |
a <= b |
a is less than or equal to b |
a == b |
a equals b (note the double =) |
a != b |
a does not equal b |
3 > 2
true
1 != 2
true
Combine comparisons with and, or and not:
3 > 2 and 2 < 3
true
not 3 > 2
false
1.5 Variables
Give a name to a value with a single =:
x = 5
5
epher repeats the value back to you. From now on, x can be used anywhere:
x ^ 2
25
You can change a variable whenever you like. It keeps its value until you change it:
x = x + 1
6
Names can contain letters and underscores, like
radiusormy_total. They cannot contain spaces or start with a number.
One name is reserved: i, the imaginary unit (section 1.18).
The special variable ans always holds the previous answer, like the
Ans key on a pocket calculator, handy for chained calculations:
2 + 3
ans * 2
5
10
One list can fill several names at once, {a, b} = list takes a list
apart, left to right (section 1.11 shows functions that hand back more
than one answer this way):
{a, b} = {10, 20}; a + b
{10, 20}
30
A position you do not want is written _:
{x, _} = {7, 8}; x
{7, 8}
7
1.6 Constants: names that never change
A constant is a name for a value that never changes, like the built-in
pi, but chosen by you. Define one with const:
const tax = 0.2
0.2
Use it anywhere a number can go:
100 * (1 + tax)
120
The value is fixed: changing it with = is an error,
tax = 0.25
error: cannot assign to constant tax
and so is redefining it with a different value:
const tax = 0.25
error: constant already defined: tax
Constants are different from variables in one more way: like pi, they
work inside your own functions.
const g = 9.81
9.81
def weight(m) = m * g
weight(80)
784.8
Save a constant for future sessions with save tax, exactly like a
function (chapter 4.4).
A variable and a constant cannot share a name: after
const tax = 0.2,tax = ...is always an error. Pick a fresh name or start a new session.
1.7 Strings and print
A string is text in double quotes: "hello". Strings concatenate with +, compare with == and !=, order with < and > (dictionary order), count with len, and index 1-based like lists:
"hello" + " " + "world"
len("hello")
"hello"[1]
"abc" == "abd"
"apple" < "banana"
hello world
5
h
false
true
A backslash inside a string starts an escape: \n is a new line, \t
a tab, and \\ and \" are the backslash and the quote themselves, so a
string can contain a double quote:
s = "say \"hi\""
len("a\nb")
say "hi"
3
str(x) spells one value the way the answer panel would, and print(a, b, …) joins its arguments with spaces into one line:
print("x =", 42)
x = 42
A small library covers the rest of report writing. upper and lower change case, trim strips spaces from the ends, substr takes a piece (1-based, like every index), find reports where a text appears (0 when it does not), replace swaps every copy of one text for another, split breaks a text into a list at a separator, join glues a list into one text, and fixed spells a number with exactly as many decimal places as you ask, the zero a report wants, kept:
upper("hello")
substr("2026-09-17", 1, 4)
find("hello world", "world")
replace("2026-09-17", "-", "/")
split("a,b,c", ",")
join({1, 2, 3}, "-")
fixed(3.1, 2)
HELLO
2026
7
2026/09/17
{a, b, c}
1-2-3
3.10
1.8 Decisions with if
if chooses between two values:
if 3 > 2 then 10 else 20
10
The shape is always if condition then value_if_true else value_if_false.
The else part is required.
A more useful example with a variable:
price = 100
if price > 50 then 2 else 1
100
2
An
ifcan compare strings too (section 1.7): both branches just have to be values of the same kind: numbers with numbers, strings with strings.
1.9 Loops with while
while repeats a statement as long as a condition holds:
x = 0; while x < 5 do x = x + 1; x
0
5
Read that script as: start x at 0; while x is less than 5, add 1 to x; then show x. The result is 5 because the loop ran five times.
Safety net: epher stops any loop after 100,000 steps and shows
error: step limit exceeded. That protects you from loops that would never end. If you see it, your condition probably never became false.
A loop can also be left on purpose: the next section adds break and
continue, and a function adds return (section 1.11).
1.10 Loops with for
for repeats a statement once per value, collecting the body's values into a list: over a range start to end (inclusive) with an optional step, or over the elements of a list:
for k in 1 to 5 do k^2
for x in {2, 3, 4} do 10*x
for k in 0 to 1 step 0.5 do k
{1, 4, 9, 16, 25}
{20, 30, 40}
{0, 0.5, 1}
The loop variable is scoped to the loop: afterwards the name means what it meant before, while assignments to other names inside the body persist. With print, a loop writes readable lines:
for k in 1 to 3 do print("line", k)
{line 1, line 2, line 3}
The same 100,000-step safety net bounds a for loop as a while.
Leaving a loop early. break stops the loop on the spot; the values
collected so far are the loop's answer. continue skips the rest of the
pass and moves to the next value. Both are statements, so they sit behind
an if:
for k in 1 to 6 do if mod(k, 2) == 1 then k
for k in 1 to 10 do if k == 4 then break else k
total = 0
for k in 1 to 6 do if mod(k, 2) == 0 then continue else total = total + k
total
{1, 3, 5}
{1, 2, 3}
0
{1, 4, 9}
9
Read the middle one as: for k from 1 to 10, if k is 4 stop; otherwise hand
back k. The loop's answer is the values it saw before it stopped. The last
pair shows continue counting: the for line's collected list is the
running total - 1, then 4, then 9 - and the even passes were skipped, so
they added nothing. The final total, 9, is the sum of the odd values. An
if without an else contributes nothing when its condition is false,
which is how the first loop keeps only the odd values, and how a loop can
filter a list as well as transform it.
1.11 Your own functions with def
A function is a calculation with a name and parameters:
def f(x) = x ^ 2
Then use it:
f(7)
49
Functions can take several parameters:
def area(w, h) = w * h
area(3, 4)
12
You can also define a function with no parameters:
def answer() = 42
answer()
42
A body with several steps. When one expression is not enough, give the
function a do … end body. The statements run one after another in order,
and the last one's value is the answer:
def hyp(a, b) do
c = a ^ 2 + b ^ 2
sqrt(c)
end
hyp(3, 4)
5
The intermediate name c lives inside the call; it is not visible outside,
and two calls do not see each other's c.
return: answer now. return value answers immediately and skips the
rest of the body, the natural shape for a choice with an early exit:
def grade(s) do
if s >= 90 then return "A"
if s >= 80 then return "B"
"C"
end
grade(95); grade(85); grade(40)
A
B
C
return also leaves a loop that is searching, the moment it finds:
def firstsq(xs) do
for x in xs do
if x ^ 0.5 == floor(x ^ 0.5) then return x
0
end
firstsq({3, 5, 9, 11}); firstsq({3, 5, 7})
9
0
One rule keeps the blocks readable: the end always closes the function's
do, an if, for, or while takes no end. A body that ends without an
answer (say, every path returned nothing) is an error, not a silence:
epher says so and names the function.
More than one answer? Give back a list, and name it in one move with the destructuring from section 1.5:
{mean, sd} = {4, 1.6}.
1.12 Recursion: a function that calls itself
The most famous example is the Fibonacci numbers:
def fib(n) = if n <= 1 then n else fib(n - 1) + fib(n - 2)
fib(10)
55
fib(10) is the 10th Fibonacci number. The function calls itself with
smaller arguments until it reaches n <= 1. This works because the
if ... then ... else ... form only calculates the branch it needs.
A function's body is one expression after
=, or ado … endblock of several statements withreturnfor early exits (section 1.11). Combine several calculations in a script instead (next section) when no function is needed.
1.13 Scripts: several statements at once
A script is several statements joined with ; or with newlines,
which mean exactly the same thing, executed one after another:
x = 10; y = x + 5; x + y
10
15
25
Scripts are how you build small programs: set up variables, loop, and show a final result.
Newlines and ; are the same separator, and you can mix them freely. The
Copy button above a multi-line example copies the whole script, and you
can paste it straight into epher: the entry field on the web app and the
desktop app, the terminal UI, and epher repl all run each line in order,
exactly as if you had typed them one by one. Joining several statements with
; on one line works everywhere too, including the one-shot command line
(section 4.1).
Scripts can carry comments - notes for you that epher skips, written the PHP way. // or # comments to the end of the line, and /* ... */ comments out a block. A note about one statement sits at its end, after //, never inside the statement:
// a small script with notes
r = 3 # radius in metres
area = pi * r ^ 2 // pi r squared
area
A block comment may also span several lines. A script file or a pasted program arrives as one whole program, so its block comments may span lines:
/* the area of a circle
of radius 3 */
pi * 3 ^ 2
Input that arrives line by line - typing into the REPL or the terminal UI, or a piped script - takes one line at a time, so there a block comment opens and closes on the same line.
1.14 Exact results: frac, dec and big
Normally epher calculates with decimal numbers like a pocket
calculator, and results round to twelve significant digits the way a
pocket calculator shows them: 0.1 + 0.2 is 0.3, never
0.30000000000000004. Exact fractions are on by default; a result
with a good small-denominator fraction whose decimal keeps repeating
shows as one. 1 / 3 displays as 1/3 without asking:
1 / 3
1/3
With exact fractions off in the Results settings (chapter 2.2) the
same division shows 0.333333333333. frac(n, d) makes an exact
fraction that stays exact through calculations:
frac(1, 3)
1/3
Fractions stay exact through calculations:
frac(1, 3) * 3
1
dec(x) makes an exact decimal. 0.1 + 0.2 shows 0.3 either way,
the difference is arithmetic:
0.1 * 3 - 0.3
dec(0.1) * 3 - dec(0.3)
0.0000000000000000555111512313
0.0
The float result carries the tiny rounding error every computer makes
with decimal numbers; dec() keeps the arithmetic exact.
big(x) makes an exact whole number, for values too large for a pocket calculator:
big(10 ^ 20)
100000000000000000000
Number bases write integers the way the math community spells them:
0b for binary, 0o for octal, 0x for hex (the prefix changes the
spelling, never the value):
0b1010 + 0xFF
265
Convert back with bin(x), oct(x) and hex(x). These give the prefixed spelling of a whole number, ready to feed straight back in:
hex(255)
bin(10)
0xff
0b1010
exact(x) reconstructs the exact fraction behind a decimal result:
any value with a good small-denominator fraction shows it. This is the
same reconstruction the apps use for their default display, so 1 / 3
usually shows as 1/3 without asking:
exact(0.3333333333333333)
exact(0.30000000000000004)
1/3
3/10
An irrational value like pi has no good fraction, so exact() leaves
it alone.
The display verbs spell a number in other notations. scientific(x) uses one digit before the exponent, engineering(x) uses exponents in steps of three (the mantissa stays between 1 and 1000), and grouped(x) inserts thin-space thousands separators:
scientific(12345)
engineering(12345)
engineering(0.5)
grouped(1234567.89)
1.2345e4
12.345e3
500e-3
1 234 567.89
The web app and TUI also offer these as display settings (see chapter 2.2 and 5.2): exact fractions on or off, Auto/Scientific/ Engineering notation, and thousands separators. The settings only change how results are shown; the values stay decimal numbers underneath.
1.15 Built-in functions
epher has the functions of a scientific calculator, grouped by family.
Trigonometry works in radians. Use deg and rad to convert:
| Function | Meaning | Example | Result |
|---|---|---|---|
sin(x), cos(x), tan(x) |
trigonometric functions | sin(pi / 2) |
1 |
asin(x), acos(x), atan(x) |
inverse trigonometric | atan(1) |
0.7853981633974483 |
atan2(y, x) |
angle of the point (x, y) | atan2(1, 1) |
0.7853981633974483 |
deg(x) |
radians → degrees | deg(pi) |
180 |
rad(x) |
degrees → radians | rad(180) |
3.14159265359 |
sinh(x), cosh(x), tanh(x) |
hyperbolic functions | sinh(1) |
1.1752011936438014 |
asinh(x), acosh(x), atanh(x) |
inverse hyperbolic | acosh(1) |
0 |
Powers, roots and logarithms (on a calculator log is base 10):
| Function | Meaning | Example | Result |
|---|---|---|---|
sqrt(x) |
square root | sqrt(16) |
4 |
cbrt(x) |
cube root | cbrt(-27) |
-3 |
root(n, x) |
nth root | root(3, 8) |
2 |
exp(x) |
e to the power x | exp(1) |
2.71828182846 |
ln(x) |
natural logarithm | ln(e) |
1 |
log(x) |
base-10 logarithm | log(100) |
2 |
log2(x) |
base-2 logarithm | log2(8) |
3 |
logb(b, x) |
logarithm in base b | logb(2, 8) |
3 |
hypot(a, b) |
hypotenuse | hypot(3, 4) |
5 |
5! (also fact(n)) |
factorial | 5! |
120 |
Rounding, signs and whole numbers:
| Function | Meaning | Example | Result |
|---|---|---|---|
abs(x) |
absolute value | abs(-3) |
3 |
floor(x) / ceil(x) |
round down / up | floor(2.7) |
2 |
round(x) |
nearest, half away from zero | round(2.5) |
3 |
trunc(x) |
drop the fraction | trunc(-2.9) |
-2 |
sign(x) |
-1, 0 or 1 | sign(-5) |
-1 |
ncr(n, r) |
combinations | ncr(52, 5) |
2598960 |
npr(n, r) |
permutations | npr(5, 2) |
20 |
gcd(a, b) / lcm(a, b) |
common divisors and multiples | gcd(12, 18) |
6 |
mod(a, b) |
remainder | mod(7, 3) |
1 |
Primes and divisors work on whole numbers:
| Function | Meaning | Example | Result |
|---|---|---|---|
isprime(n) |
true when n is prime | isprime(97) |
true |
nextprime(n) / prevprime(n) |
nearest primes | nextprime(10) |
11 |
factors(n) |
prime factorization | factors(360) |
2^3 * 3^2 * 5 |
totient(n) |
Euler's totient | totient(12) |
4 |
ndivisors(n) |
how many divisors | ndivisors(360) |
24 |
modpow(b, e, m) |
b to the e, mod m, exactly | modpow(2, 10, 1000) |
24 |
Statistics take any number of arguments:
| Function | Meaning | Example | Result |
|---|---|---|---|
sum(...) / product(...) |
totals | sum(1, 2, 3) |
6 |
mean(...) |
average | mean(1, 2, 3) |
2 |
median(...) |
middle value | median(1, 2, 3, 4) |
2.5 |
min(...) / max(...) |
smallest / largest | max(4, 1, 3) |
4 |
variance(...) / stdev(...) |
spread of the values | stdev(2, 4) |
1 |
The exact layers from section 1.14 stay:
| Function | Meaning | Example | Result |
|---|---|---|---|
frac(n, d) |
exact fraction | frac(1, 3) |
1/3 |
dec(x) |
exact decimal | dec(0.1) |
0.1 |
big(x) |
exact whole number | big(10 ^ 20) |
100000000000000000000 |
| Binary, octal, hex | 0b…, 0o…, 0x… |
0xFF + 0b1 |
|
| Base spelling | bin(x), oct(x), hex(x) |
hex(255) |
|
bin(x) / oct(x) / hex(x) |
prefixed spelling in base 2 / 8 / 16 | hex(255) |
0xff |
They combine like everything else:
min(sqrt(16), 5)
4
The physical constants use SI units, like the astronomy ones in section 1.29:
| Name | Meaning | Value |
|---|---|---|
G |
Newton's gravitational constant | 6.6743e-11 |
gamma |
Euler-Mascheroni constant | 0.577215664902 |
q_e |
elementary charge | 1.602176634e-19 |
ev |
electronvolt, in joules | 1.602176634e-19 |
eps_0 |
vacuum permittivity | 8.8541878128e-12 |
mu_0 |
vacuum permeability | 1.25663706212e-6 |
z_0 |
impedance of free space | 376.730313668 |
m_e |
mass of the electron | 9.1093837139e-31 |
m_p |
mass of the proton | 1.67262192595e-27 |
m_n |
mass of the neutron | 1.67492750056e-27 |
m_u |
atomic mass unit | 1.66053906892e-27 |
a_0 |
Bohr radius | 5.29177210544e-11 |
alpha |
fine-structure constant | 0.0072973525643 |
r_inf |
Rydberg constant | 10973731.568160 |
mu_b |
Bohr magneton | 9.2740100783e-24 |
n_a |
Avogadro constant | 6.02214076e23 |
faraday |
Faraday constant, C/mol | 96485.33212 |
r_gas |
molar gas constant | 8.31446261815 |
atm |
standard atmosphere, in pascals | 101325 |
wien |
Wien wavelength constant | 0.002897771955 |
phi_0 |
magnetic flux quantum | 2.067833848e-15 |
m_P |
Planck mass | 2.176434e-8 |
l_P |
Planck length | 1.616255e-35 |
t_P |
Planck time | 5.391247e-44 |
r_e |
classical electron radius | 2.8179403205e-15 |
lambda_c |
Compton wavelength | 2.42631023867e-12 |
mu_n |
nuclear magneton | 5.050783699e-27 |
1.16 Reading errors
When something goes wrong, epher tells you instead of guessing:
1 / 0
error: division by zero
sqrt(-4)
2i
unknown_name
error: unknown name: unknown_name
foo(1)
error: unknown name: foo
The last example is important: epher tells you exactly which name it does not know, so you can fix your expression.
1.17 Quick reference
| What | Syntax | Example |
|---|---|---|
| Add, subtract, multiply, divide | + - * / |
7 / 2 |
| Power | ^ (right-to-left) |
2 ^ 10 |
| Factorial | ! (postfix) |
5! |
| Percent | % (postfix) |
200 * (1 + 10%) |
| Parentheses | ( ) |
(2 + 3) * 4 |
| Constants | pi, e, tau, phi |
2 * pi |
| Scientific notation | 2.5e-3 |
6.02e23 |
| Compare | > < >= <= == != |
3 >= 2 |
| Logic | and or not |
a > 1 and a < 10 |
| Variable | name = value |
x = 5 |
| Constant | const name = value |
const tax = 0.2 |
| Decision | if c then a else b |
if x > 0 then 1 else -1 |
| Choose statements | if c then stmt [else stmt] |
if k == 4 then break |
| Loop | while c do statement |
while x < 5 do x = x + 1 |
| For loop | for k in a to b step s do stmt |
for k in 1 to 5 do k^2 |
| Leave / skip a loop | break, continue |
if k == 4 then break |
| Function | def name(params) = expr or do … end |
def f(x) = x ^ 2 |
| Answer now | return value |
if ok then return x |
| Several names | {a, b} = list (_ skips) |
{m, sd} = stats(d) |
| Strings | "...", + joins, s[k], ==, < |
"a" + "b" |
| String library | upper lower trim substr split join find replace fixed |
split("a,b", ",") |
print(a, b, ...) |
print("x =", 42) |
|
| Script | statements joined with ; or newlines |
x = 1; x + 1 |
| Exact fraction | frac(n, d) |
frac(1, 3) |
| Exact decimal | dec(x) |
dec(0.1) + dec(0.2) |
| Exact whole number | big(x) |
big(10 ^ 20) |
| Reconstruct a fraction | exact(x) |
exact(0.3333333333333333) |
| Scientific, engineering, grouped | scientific(x) engineering(x) grouped(x) |
engineering(12345) |
| Imaginary unit | i, or a literal 4i |
sqrt(-1) |
| Complex parts | re(z) im(z) arg(z) conj(z) abs(z) |
re(3 + 4i) |
| Solve an equation | solve lhs == rhs |
solve x^2 == 9 |
| Numeric derivative | derivative(expr, x) |
derivative(x^2, 3) |
| Definite integral | integral(expr, a, b) |
integral(x^2, 0, 3) |
| Binary, octal, hex | 0b…, 0o…, 0x… |
0xFF + 0b1 |
| Base spelling | bin(x), oct(x), hex(x) |
hex(255) |
| Primes | isprime(n), factors(n), … |
factors(360) |
| List literal | {…} |
{1, 2, 3} |
| List element | list[k] (1-based) |
{5, 6}[2] |
| List statistics | mean(list), median(list), … |
stdev(d) |
| List shape | len(s), sort(s), mode(s), range(s), quartile(s, k) |
quartile(d, 1) |
| Linear regression | linreg(xs, ys) |
linreg(x, y) |
| Regression family | quadreg expreg powreg logreg |
quadreg(xs, ys) |
| Normal family | normpdf normcdf invnorm |
invnorm(0.975) |
| t family | tpdf tcdf invt |
invt(0.975, 10) |
| Chi-squared family | chi2pdf chi2cdf invchi2 |
chi2cdf(3.84, 1) |
| Discrete families | binompdf binomcdf poissonpdf poissoncdf |
binomcdf(2, 10, 0.5) |
| Tests and intervals | ztest ttest zinterval tinterval chisq_gof |
tinterval(d, 0.95) |
| ANOVA and paired t | anova(lists...), ttestpaired(a, b) |
anova(g1, g2, g3) |
| Data plots | graph scatter(xs, ys) histogram(data) boxplot(data) |
graph boxplot(d) |
| Random numbers | random(), random(a, b), randint(a, b), randseed(n) |
randint(1, 6) |
| Normal draws | randn(mu, sigma) |
randn(0, 1) |
| Constants browser | Help → Constants: every builtin constant, grouped | Help → Constants |
| Quantity | 5 m, 60 mile/hr, 1 km |
2 m^2 |
| Convert | expr in unit or expr -> unit |
72 km/hr in m/s |
| Prefixes | k M G T m µ n p scale any unit |
5 km, 3 MPa, 1 GHz |
| Bitwise and, or | a & b, a | b |
0xFF & 0x0F |
| Bitwise xor | a xor b |
5 xor 3 |
| Bitwise not | ~a |
~0 |
| Shifts | a << n, a >> n |
1 << 8 |
| Word size | bits(n) for 8, 16, 32, 64 |
bits(8) |
| Implicit relation | graph lhs == rhs |
graph x^2 + y^2 == 1 |
| Matrix literal | [[1, 2], [3, 4]] |
[[1, 2], [3, 4]] * [[5, 6], [7, 8]] |
| Matrix functions | det inv transpose trace dim ref rref |
rref([[2, 1, 5], [1, -1, 1]]) |
| TVM solver | tvm_n tvm_i tvm_pv tvm_pmt tvm_fv |
tvm_pmt(360, 0.08/12, -100000, 0) |
| NPV and IRR | npv(rate, flows) irr(flows) |
irr({-100, 60, 60}) |
| Amortization | amort(p, r, n, k) |
amort(1000, 0.01, 12, 6) |
| Interest | simple_interest compound_interest |
compound_interest(1000, 0.05, 2) |
1.18 Complex numbers
epher calculates with complex numbers automatically. The imaginary
unit is i, exactly like pi:
i ^ 2
sqrt(-1)
-1
i
Unlike every other built-in name, i cannot be reused: i = 5 is refused, so the imaginary unit can never be shadowed - not by an assignment, not by an old saved session.
Write a complex number with the i suffix, no multiplication sign
needed: 3 + 4i is one literal, 2.5i works, and so do the based
literals (0xFFi). The usual arithmetic extends: add, subtract,
multiply, divide, and powers all work, and i follows the normal
precedence (i ^ 2 binds like any power).
The real functions extend too. Given a complex argument they compute in the complex plane; given a real argument outside their real domain they return the principal complex result instead of an error:
ln(-1)
asin(2)
exp(i * pi)
3.14159265359i
1.57079632679-1.31695789692i
-1+0.000000000000000122464679915i
(exp(i * pi) is exactly -1; the last digits are the noise of
sin(pi) in the computer's arithmetic.)
Four functions read a complex number's parts, and abs() is its
magnitude:
re(3 + 4i)
im(3 + 4i)
arg(-1)
conj(3 - 4i)
abs(3 + 4i)
3
4
3.14159265359
3+4i
5
Integer-only functions (fact, gcd, floor, isprime, ...) reject
complex arguments with a type error.
1.19 Solving equations
solve finds the roots of an equation in one variable. The equation
uses ==:
solve x^2 == 5*x + 6
x = -1, x = 6
Polynomial equations (built from + - * ^ and constants) give every
root, real and complex:
solve x^2 == -1
solve x^2 + 2*x + 5 == 0
solve (x - 1)^2 == 0
x = -i, x = i
x = -1-2i, x = -1+2i
x = 1
The variable solved for is x when it appears, otherwise the single
other variable. Constants and bound variables act as parameters:
const k = 3
solve k*x == 12
3
x = 4
Any other equation is scanned numerically over -100..100: roots are
found by bracketing sign changes, so solve sin(x) == 0.5 lists every
root in that range. Two honest limitations: a root where the function
only touches zero (like x^2 == 0 through the numeric path) can be
missed, and equations in several unbound variables are an error.
1.20 Calculus: derivative and integral
derivative(expr, p) is the numeric derivative of expr at p. The
first argument stays an expression, and its free variable is the one
differentiated:
derivative(x^2, 3)
derivative(sin(t), 0)
6
1
Because the argument stays an expression, the derivative is graphable:
graph derivative(x^3 - x, x) plots the slope curve.
integral(expr, a, b) is the definite integral from a to b,
computed by adaptive Simpson quadrature:
integral(x^2, 0, 3)
integral(sin(x), 0, pi)
9
2
integral(x^2, 3, 0) is -9 (the signed integral), and a graphable
upper bound works: graph integral(x^2, 0, x).
Both are numeric; the expressions must be real-valued over the range, and an expression in several variables is an error.
1.21 Data: lists, statistics, and regression
A list is a column of numbers in braces: {1, 2, 3}. Elements are
expressions, the empty list {} is allowed, and a list binds to a name
like any value:
d = {12, 15, 14, 16, 13, 15, 14, 17}
d[2]
len(d)
{12, 15, 14, 16, 13, 15, 14, 17}
15
8
list[k] is the k-th element, 1-based like a calculator expects; an
out-of-range index is an error. The bracket binds tighter than ^, so
d[2]^2 is (d[2])^2.
Arithmetic over a list is elementwise, with a plain number broadcast to every element:
{1, 2, 3} * 2
{1, 2, 3} + 10
{2, 4, 6}
{11, 12, 13}
Two lists must have the same length for + - * / ^. == and !=
compare whole lists; ordering comparisons reject them.
The statistics functions take a list as their one argument (they keep
their variadic form too, mean(1, 2, 3) still works): sum product mean median mode variance stdev min max range. The new shape
functions are len(list), sort(list) (ascending copy), mode(list)
(most frequent value, smallest on ties), range(list) (max minus
min), and quartile(list, k) for k in 1..3 (TI-style median of
halves):
mean(d)
median(d)
quartile(d, 1)
14.5
14.5
13.5
linreg(xs, ys) fits the least-squares line through two same-length lists and reports it with the correlation r:
linreg({1, 2, 3, 4}, {2.1, 4.2, 5.8, 8.1})
y = 1.96*x + 0.15 (r = 0.9979)
The fitted line is a display, like solve's roots; the picture of the fit lives on the scatter plot (section 1.23).
The rest of the regression family fits the models calculators grow into: quadreg fits y = a*x^2 + b*x + c (at least 3 points), expreg fits y = a*e^(b*x) (y > 0), powreg fits y = a*x^b (x and y > 0), and logreg fits y = a + b*ln(x) (x > 0). Each reports the model with its r:
quadreg({1, 2, 3, 4}, {1, 4.1, 8.9, 16.2})
expreg({1, 2, 3}, {2.7, 7.4, 20.1})
y = 1.05*x^2 + -0.21*x + 0.2 (r = 0.9999)
y = 0.9911*e^(1.0037*x) (r = 1)
The r of a transformed fit is the correlation of the linearized pair: the same number TI and NumWorks report. Every model can also draw itself over a scatter: graph scatter(xs, ys, quadreg) (or expreg, powreg, logreg) plots the points with that model's curve.
1.22 Distributions and hypothesis tests
The probability functions cover the standard normal, Student's t, chi-squared, binomial, and Poisson families. The normal family takes one or three arguments, one argument is the standard normal:
normcdf(1.96)
invnorm(0.975)
normcdf(12, 10, 2)
0.975002104852
1.95996398454
0.841344746069
normpdf(x[, mu, sigma]), normcdf(x[, mu, sigma]), invnorm(p[, mu, sigma]); tpdf(x, df), tcdf(x, df), invt(p, df);
chi2pdf(x, df), chi2cdf(x, df), invchi2(p, df);
binompdf(k, n, p), binomcdf(k, n, p); poissonpdf(k, lambda),
poissoncdf(k, lambda). The inv* functions answer the reverse
question: invt(0.975, 10) is the t value with 97.5% of the mass
below it.
The tests take a data list and report the statistic and the two-sided
p-value as a display string; the intervals report (lo, hi) at the
level you name:
d = {12, 15, 14, 16, 13, 15, 14, 17}
ttest(d, 14)
tinterval(d, 0.95)
ztest(d, 14, 1.5)
chisq_gof({20, 30, 25, 25}, {25, 25, 25, 25})
{12, 15, 14, 16, 13, 15, 14, 17}
t = 0.8819, p = 0.4071
(13.1594, 15.8406)
z = 0.9428, p = 0.3458
chi2 = 2, p = 0.5724
ttest(data, mu0) and tinterval(data, level) use the sample
standard deviation (n−1); ztest(data, mu0, sigma) and
zinterval(data, sigma, level) need the known sigma.
chisq_gof(observed, expected) is the goodness-of-fit test with k−1
degrees of freedom. The results are display strings, so they are
readable and copy-pasteable, but arithmetic cannot touch them.
Two more tests share the same shape. anova(list1, list2, …) is one-way analysis of variance over two or more groups (unequal lengths are fine) and reports F with its p-value:
anova({1, 2, 3}, {4, 5, 6}, {7, 8, 9})
F = 27, p = 0.001
ttestpaired(a, b) is the paired t-test: it takes the differences of two same-length lists and tests them against 0, the classroom "before and after" test:
ttestpaired({80, 85, 90}, {82, 84, 91})
t = -0.7559, p = 0.5286
1.23 Data plots
The graph family takes lists too: a scatter, a histogram, and a
box-and-whisker plot. A data plot owns the pane like a solar system
does, the newest command wins, and graph clear empties it.
x = {1, 2, 3, 4, 5}
y = {2.1, 4.2, 5.8, 8.1, 9.9}
graph scatter(x, y)
graph histogram({1, 2, 2, 3, 3, 3, 4, 5})
graph boxplot({1, 2, 2, 3, 3, 3, 9})
scatter(xs, ys) plots the points and, with two or more points,
draws the least-squares fit line, captioned y = a*x + b (r = …) in
the legend. histogram(data[, bins]) draws a frequency histogram;
the bin count is optional (Sturges' rule by default) and must be a
whole number between 1 and 50. boxplot(data) draws the
box-and-whisker: min, Q1, median, Q3, max, with whiskers to the
extremes. The plot window opens fitting the data (the from a to b
domain keywords still do not apply), and once it is drawn the picture
zooms exactly like a curve plot: the mouse wheel, a pinch, and the
zoom slider all work, and the export saves what the pane shows.
An optional third word chooses the model: graph scatter(xs, ys, quadreg) (or expreg, powreg, logreg) draws that fit instead of the line.
1.24 Random numbers
random() draws a uniform random number in [0, 1), random(a, b)
one in [a, b), and randint(a, b) a whole number from the closed
range [a, b], a dice roll:
randseed(7)
randint(1, 6)
7
3
The sequence is reproducible: randseed(n) re-seeds the generator
with n and reports it, so the same seed replays the same draws in
every session and every frontend.
randn(mu, sigma) draws from the normal distribution with mean mu and standard deviation sigma (Desmos calls it randomNormal, TI calls it randNorm):
randseed(7)
randn(0, 1)
7
1.36499229746
The same seed replays the same draws, exactly like the uniform ones.
1.25 Units and conversion
A number followed by a unit becomes a quantity: the value in SI
units plus its dimensions. The unit table covers the SI base and
derived units (m, s, kg, A, K, mol, cd, Hz, N, Pa,
J, W, C, V, F, ohm, S, Wb, T, H, lm, lx, Bq,
Gy, Sv), the everyday units (min, hr, d, yr, L, t,
bar, atm, torr, psi, eV, mile, yd, ft, inch, nmi,
lb, oz, gal, qt, pt, mph, knot), and the astronomy
suffixes from section 1.29. Compound units chain: 60 mile/hr and
5 m/s^2 are single units.
60 mile/hr
60 mile/hr
The SI prefixes scale any of them: k M G T m µ n p are kilo, mega,
giga, tera, milli, micro, nano, pico, 5 km, 3 MPa, 1 GHz all
work, and 2 kg is the kilogram itself.
The dimensions are checked: adding or comparing quantities with different units errors instead of mixing metres and seconds:
5 m + 3 s
error: dimension error: cannot add 5 m and 3 s
Arithmetic composes the dimensions: 5 m * 3 m is 15 m^2,
(3 m)^2 is 9 m^2, sqrt(4 m^2) is 2 m, and a whole expression
whose dimensions cancel is an ordinary number again (5 m / 5 m is
1). Results prefer the exact derived name when the dimensions match
one, 5 kg * 3 m / 1 s^2 answers 15 N.
Conversion. expr in unit (or expr -> unit) shows a quantity in
the named unit; the dimensions must match. in binds loosest of the
operators, so 5 m + 3 m in km converts the whole sum:
72 km/hr in m/s
20 m/s
2 m^2 in cm^2
20000 cm^2
Temperature scales (Celsius, Fahrenheit) are not units here, kelvins
are, and K works like any other.
1.26 Bitwise operations
The base literals from section 1.15 are made for it: 0b101, 0o17,
0xFF. The bitwise operators work on whole numbers and answer with
exact integers:
0xFF & 0x0F
15
| Operator | Meaning |
|---|---|
a & b |
bitwise and |
a | b |
bitwise or |
a xor b |
bitwise exclusive or |
~a |
bitwise not (two's complement) |
a << n |
shift left (multiply by 2^n) |
a >> n |
shift right, arithmetic (divide by 2^n, rounding down) |
The results are exact big integers, so 1 << 60 keeps every digit.
The working word size is 64 bits by default: results are read as
signed two's complement, so ~0 is -1 and 1 << 100 wraps to 0.
bits(n) changes the word size to 8, 16, 32, or 64, and bits()
reports it:
bits(8)
~0
8
-1
Shifts by a negative amount reverse the direction (8 << -1 is 4).
The boolean and and or keep their meanings; & and | are the
bitwise spellings.
1.27 Implicit relations
An equation in two unknowns plots as a curve: the graph family samples the relation with marching squares and draws its zero contour. The circle, the parabola, and the vertical line are all one command each:
graph x^2 + y^2 == 1
graph y == x^2
graph x == 2
The relation is sampled over the square from from a to b (or the
default window), so graph x^2 + y^2 == 1 from -2 to 2 fits the
circle's window. Everything a curve can do applies: the legend
captions the equation, sliders animate its constants, and the picture
zooms, pans, and exports like any other plot. The inequality fills
(y < …, y > …) stay curves with shading; a relation has no points
of interest.
1.28 Matrices
A matrix is a grid of numbers, spelled as rows of lists: [[1, 2], [3, 4]] is the 2×2 matrix. + and - are elementwise (matching
shapes), * is the matrix product, a number scales elementwise, and
^ is the whole-number matrix power (A ^ 0 is the identity, so
powers need square matrices). M[2][1] is the element at row 2,
column 1, rows index like lists, 1-based.
[[1, 2], [3, 4]] * [[5, 6], [7, 8]]
[[19, 22], [43, 50]]
The matrix functions cover the classroom floor: det(M) (square
only), inv(M) (singular matrices are an error), transpose(M),
trace(M) (square), dim(M) (the {rows, cols} list), and ref(M)
with rref(M) for row reduction. Linear systems solve through rref
on the augmented matrix:
rref([[2, 1, 5], [1, -1, 1]])
[[1, 0, 2], [0, 1, 1]]
The rows read x = 2, y = 1, the last column of the reduced
augmented matrix. Exact fractions display inside matrices like lists,
so inv([[1, 2], [3, 4]]) shows [[-2, 1], [3/2, -1/2]].
1.29 Astronomy and the solar system
epher speaks astronomy: unit suffixes, physical constants, calendar and time functions, and a live ephemeris for the Sun, the Moon, the planets and Pluto. Everything works offline.
Units that speak astronomy. Write a number followed by a unit suffix and epher converts it to SI units on the spot:
| Suffix | Unit | Converts to |
|---|---|---|
AU or au |
astronomical unit | metres |
pc |
parsec | metres |
ly |
light year | metres |
deg |
degree | radians |
arcmin, arcsec |
arcminute, arcsecond | radians |
min, hr, d, yr |
minute, hour, day, Julian year | seconds |
Jy |
jansky | W m-2 Hz-1 |
3.2 AU in m
478713186240 m
sin(30 deg)
0.5
One worked example per suffix, to copy from:
| Suffix | Try | Result |
|---|---|---|
AU, au |
1 au in km |
149597870.7 km |
pc |
pc / au |
206264.806247 (au per parsec) |
ly |
ly / au |
63241.0770843 (au per light year) |
deg |
90 deg in rad |
1.57079632679 |
arcmin |
sin(1 arcmin) |
0.000290888204563 |
arcsec |
sin(1 arcsec) |
0.00000484813681108 |
min |
1 min in s |
60 s |
hr |
1 hr in min |
60 min |
d |
1 d in hr |
24 hr |
yr |
1 yr in d |
365.25 d |
Jy |
mag2jy(0) Jy |
3631 Jy |
The suffixes are part of the grammar, so no user constant can change what
3.2 AU means, and h stays Planck's constant: hours are written hr.
Functions return counts in natural units; a suffix converts a count to SI,
so mag2jy(20) is a jansky count and mag2jy(20) Jy is the same flux in
watts per square metre hertz.
Astronomy constants. au, pc, ly, c, g, h, h_bar, k_b,
sigma_sb, m_sun, r_sun, l_sun, m_earth, r_earth, m_moon, r_moon work like pi,
and you can shadow them with your own constants. One example per constant,
to copy from:
| Constant | Quantity | Try | Result |
|---|---|---|---|
au |
astronomical unit | 1 au in km |
149597870.7 km |
pc |
parsec | pc / au |
206264.806247 |
ly |
light year | ly / au |
63241.0770843 |
c |
speed of light (m/s) | c / 1000 |
299792.458 (km/s) |
g |
standard gravity (m/s²) | g |
9.80665 |
h |
Planck constant (J·s) | h |
0.000000000000000000000000000000000662607015 |
h_bar |
reduced Planck constant | h_bar |
0.000000000000000000000000000000000105457181765 |
k_b |
Boltzmann constant (J/K) | k_b |
0.00000000000001380649 |
sigma_sb |
Stefan-Boltzmann constant | sigma_sb * 5772 ^ 4 |
12902411456/205 (the Sun's surface flux, about 62.9 MW/m²) |
m_sun |
solar mass (kg) | m_sun / m_earth |
332954.355179 |
r_sun |
solar radius (m) | r_sun / r_earth |
109.197928112 |
l_sun |
solar luminosity (W) | l_sun / (4 * pi * (1 au) ^ 2) |
1361.16646541 (the solar constant) |
m_earth |
Earth mass (kg) | m_earth / m_moon |
81.342958322 |
r_earth |
Earth radius (m) | r_earth / r_moon |
3.66697363877 |
m_moon |
lunar mass (kg) | m_moon / m_earth |
0.0122936271391 |
r_moon |
lunar radius (m) | r_earth / r_moon |
3.66697363877 |
Dates and time. jd(y, m, d [, hr]) and mjd(...) turn a calendar date
into a Julian Date, now() reads the current instant (the value here is
from this writing; yours will differ):
jd(2000, 1, 1, 12)
2451545
mjd(2000, 1, 1, 12)
51544.5
delta_t(jd) is the TT - UT1 correction, and lst(jd, lon) is the local
sidereal time in hours for a longitude in degrees east:
delta_t(jd(2026, 1, 1))
69.1
lst(jd(2026, 6, 21), 0)
17.9472349955
Reading a Julian Date back. A Julian Date is just a day count, so it
needs translating before it reads as a date. The community's standard
conversion (Meeus, "Astronomical Algorithms", ch. 7; the IAU SOFA
jd2cal routine) turns a JD into the Gregorian calendar date and the
UT clock time; the notation is the ISO 8601 date-time that JPL
Horizons and astropy print. epher has all three forms as built-ins:
date(jd) gives the {year, month, day}, time(jd) gives the
{hour, minute, second} clock in UT, and iso(jd) spells the whole
instant as YYYY-MM-DDTHH:MM:SS text:
iso(jd(2000, 1, 1, 12))
2000-01-01T12:00:00
date(jd(2000, 1, 1, 12))
{2000, 1, 1}
time(jd(2000, 1, 1, 12))
{12, 0, 0}
The three are the exact inverse of jd(): jd(date(j) ...) of what
date() returns is the original JD again. The calendar switch of
1582-10-15 is handled: JDs from 2299160.5 onward read as Gregorian
calendar dates, earlier ones as Julian calendar dates, and iso(0) is
the epoch itself, -4712-01-01T12:00:00. time() rounds to the nearest
second; iso() lets a half-second carry into the next day so the
date-time stays true to the instant. JDs below 0 are outside the
calendar's range and raise a domain error.
Hours, minutes and seconds. hms2deg(h, m, s) converts right ascension
to degrees, dms2deg(d, m, s) converts a sexagesimal angle, and
deg2hms(x) / deg2dms(x) spell an angle back as text:
deg2hms(90)
6h 0m 0s
hms2deg(6, 42, 14)
12067/120
dms2deg(-23, 26, 0)
-703/30
deg2dms(-23.4333)
-23° 26' 0"
The sky, quantified. Give each accessor a body number: Mercury 1, Venus 2, Mars 4, Jupiter 5, Saturn 6, Uranus 7, Neptune 8, Pluto 9, Sun 10, Moon 11 (Earth is 3, the observer, never a target).
| Function | Meaning |
|---|---|
ra(b, jd), decl(b, jd) |
geocentric right ascension and declination (degrees) |
dist(b, jd) |
distance in AU |
alt(b, jd, lat, lon), az(b, jd, lat, lon) |
topocentric altitude and azimuth (degrees, true) |
rise(b, jd, lat, lon), set(...), transit(...) |
events of that local solar day, as Julian Dates |
mag(b, jd) |
apparent magnitude |
phase(b, jd), illum(b, jd) |
phase angle (degrees) and illuminated fraction |
diam(b, jd) |
angular diameter (degrees) |
satx(5, s, jd), saty(5, s, jd), satz(5, s, jd) |
a moon of Jupiter (s 1-4: Io, Europa, Ganymede, Callisto) or Saturn (s 1-8: Mimas, Enceladus, Tethys, Dione, Rhea, Titan, Hyperion, Iapetus), in the planet's radii (x west, y north, z toward you) |
satsep(5, s, jd) |
the moon's separation from the planet's centre, in arcseconds |
satphen(5, s, jd) |
the moon's state: 0 visible, 1 transit, 2 occulted, 3 in eclipse, 4 shadow transit |
decl(10, jd(2000, 6, 21, 1.8))
23.437882351
The remaining accessors at work, one example each (dates use Greenwich, 51.4779 N, 0 E):
ra(10, jd(2000, 1, 1, 12))
281.278205832
dist(4, jd(2026, 6, 1))
2.18405254195
alt(10, jd(2026, 6, 21, 11.97), 51.4779, 0)
61.950168
az(10, jd(2026, 6, 21, 11.97), 51.4779, 0)
178.2365282
mag(11, jd(2026, 6, 21))
-9.66497374724
phase(11, jd(2026, 6, 21))
100.83
illum(11, jd(2026, 6, 21))
0.4061
diam(11, jd(2026, 6, 21))
0.519678138889
The rise, set, and transit functions answer in Julian Dates; read them
back with iso() (above):
iso(rise(10, jd(2026, 6, 21), 51.4779, 0))
2026-06-21T03:42:47
iso(transit(10, jd(2026, 6, 21), 51.4779, 0))
2026-06-21T12:01:49
iso(set(10, jd(2026, 6, 21), 51.4779, 0))
2026-06-21T20:20:50
Latitudes and longitudes are degrees, east positive. Positions are geocentric unless an observer is given. Pluto rides an approximate orbit that is honest to about an arcminute, far below the accuracy of the other bodies; eclipses and conjunction searches are not included.
Optics and light. kepler(M, e) solves Kepler's equation,
airmass(alt) is the sec(z) airmass, dawes(d) is the resolving power of
a d-millimetre aperture in arcseconds, and dist_mod(mu) turns a distance
modulus into parsecs; mag2jy(m) and jy2mag(f) convert magnitude and
density:
kepler(0.3, 0.1)
0.333333124406
airmass(30)
2
dawes(100)
1.16
dist_mod(10)
1000
mag2jy(0)
3631
jy2mag(363.1)
2.5
Seasons. march_equinox(year), june_solstice(year),
september_equinox(year) and december_solstice(year) return the Julian
Date of each season boundary:
march_equinox(2000)
1012520636/413
june_solstice(2000)
2451716.57464
september_equinox(2000)
2451810.22761
iso(december_solstice(2000))
2000-12-21T13:37:50
The solar system in 3D. The solar3d command draws the whole system:
every orbit as a curve, every body as a labelled dot, with a trail showing
where it just was:
solar3d jd(2020, 7, 1)
Give the time as a constant and press the play button to watch the planets
move: const t = now(); solar3d t. Drag or use the arrow keys to orbit,
clear to empty, and solar3d save file.svg to export.
The ephemeris is computed by the solar-ephemeris crate (github.com/Protonmatter/sol), validated against JPL Horizons; thank you to its author. Accuracy is arcsecond-class for the Sun, Moon and planets over roughly 5000 years around the present.
1.30 Finance
epher speaks money as well as it speaks astronomy: a time-value-of-money
solver, loan amortization, interest, and cash-flow analysis, all working
offline. Everything in this section returns plain numbers, not currency
strings, so the answers are currency-agnostic and flow straight back
into arithmetic. Rates are always per period as a fraction: 0.08/12 is
an 8% annual rate billed monthly, and 0.01 is 1% (the % postfix from
1.2 works too: 6 * 100% is 0.06).
The sign convention. The solver follows the calculator standard (TI): money you pay out is negative, money you receive is positive. For a loan you took, the payout is negative and the payments are positive; for a savings plan, the deposits are negative and the nest egg you collect is positive. A consistent set of five fields makes the balance zero:
pv*(1+r)^n + pmt*(1+r*begin)*((1+r)^n - 1)/r + fv = 0
Mixing the signs up (both the loan and the payments negative) reads as "the money never balances", and the solver answers with a domain error rather than a nonsense number.
The five fields in plain words. The letters come from the
calculator tradition, so they are worth spelling out once. A period
is one step of the problem: a month for a monthly loan, a year for a
yearly one. The rate is written r throughout this guide, never
i - i is the imaginary unit (1.18):
| Field | Plain words |
|---|---|
n |
the number of periods: how many payments or compounding steps there are. A 30-year loan billed monthly runs n = 360. |
r |
the interest rate per period, as a fraction (0.01 is 1%). An 8% annual rate billed monthly is r = 0.08/12. |
pv |
the present value: what the loan or lump is worth today - the amount borrowed, or the price paid now. |
pmt |
the payment: the fixed amount that changes hands every period. |
fv |
the future value: the balance when the last period is done. 0 for a loan paid off; the savings goal for a plan. |
begin |
0 when payments fall at the end of each period (the default), 1 at the beginning (rent, most salaries). |
The time-value-of-money solver. Five functions answer one field with the other four given. Each name says which field it answers:
| Function | Answers |
|---|---|
tvm_pmt(n, r, pv, fv) |
the payment |
tvm_n(r, pv, pmt, fv) |
the number of periods |
tvm_i(n, pv, pmt, fv) |
the per-period rate (the one name keeping the old letter; the rate itself is always r) |
tvm_pv(n, r, pmt, fv) |
the present value |
tvm_fv(n, r, pv, pmt) |
the future value |
The classic 8% mortgage: 360 monthly payments of 733.76 against a 100,000 loan:
tvm_pmt(360, 0.08/12, -100000, 0)
733.764573879
The lifetime interest is the payments times their count minus the loan:
tvm_pmt(360, 0.08/12, -100000, 0) * 360 - 100000
164155.246597
tvm_i reads the rate back out of a quoted payment (just under 8%/12,
because 733.76 is rounded):
tvm_i(360, -100000, 733.76, 0)
0.00666661199068
tvm_n answers "how long". Paying 900 a month instead of the minimum:
tvm_n(0.08/12, -100000, 900, 0)
203.163223431
About 203 months instead of 360. And the bank's own quote checks out: a 100,000 loan at 5% quoting 536.82 a month really is 30 years:
tvm_n(0.05/12, 100000, -536.82, 0)
360.002521488
(Note the signs flipped: here the loan is money received, so it is positive and the payments are negative.)
tvm_fv grows a savings plan: 200 a month for 40 years at 6%:
tvm_fv(480, 0.06/12, 0, -200)
398298.146866
tvm_pv prices a stream of payments: what a fund must hold today to
pay 1,500 a month for 20 years at 5%:
tvm_pv(240, 0.05/12, 1500, 0)
-227287.969611
The answer is negative because buying the fund is money out today. The
other direction, how much to put aside each month to reach a goal,
asks tvm_pmt with the goal as fv: 50,000 in ten years at 5% costs
322 a month:
tvm_pmt(120, 0.05/12, 0, -50000)
321.994242862
Every one of the five takes an optional last argument begin: 0 means
payments fall at the end of each period (the default), 1 at the
beginning (an annuity due, rent, most salaries). Beginning-of-period
payments earn interest one period longer, so the mortgage payment is a
little lower:
tvm_pmt(360, 0.08/12, -100000, 0, 1)
728.90520584
The rate search caps at 100% per period and the term search at ten million periods; a problem outside those ranges (or a sign pattern that never balances) reports a domain error naming what it tried.
Amortization. amort(p, r, n, k) is the remaining balance once k
of a loan's payments have been made: p is the principal (the amount
borrowed), r the per-period rate, n the loan's full number of
payments, k how many are done. A fresh loan (k = 0) shows the
principal, a paid-off one (k = n) zero:
amort(100000, 0.08/12, 360, 120)
87724.7039064
After ten years of the 8% mortgage, 87,725 still owed. A loop (1.10) turns that into the amortization schedule, one line every five years:
for k in 0 to 360 step 60 do amort(100000, 0.08/12, 360, k)
{100000, 95069.8567174, 87724.7039064, 76781.5595143, 60477.9628062, 36188.1192209, 0}
Interest. simple_interest(p, r, t) is p*r*t and
compound_interest(p, r, n) is p*(1+r)^n - p; p is the
principal, r the rate (a year's rate in the first, a per-period
rate in the second), t the years and n the periods. Both answer
the interest earned, not the balance:
simple_interest(1000, 0.05, 2)
100
compound_interest(1000, 0.05, 2)
102.5
The balance itself is plain arithmetic, which is the point of plain numbers:
1000 * 1.05 ^ 2
1102.5
Two everyday rates built the same way. The effective annual rate of a 6% nominal rate compounded monthly, and the rule of 72's doubling time at 6%:
(1 + 0.06/12) ^ 12 - 1
0.0616778118645
72 / (6 * 100%)
12
Cash-flow analysis. npv(r, flows) discounts a cash-flow list at
the rate r per period: flows[1] is the outlay today, the rest
arrive one period apart. irr(flows) finds the rate where the net
present value is zero. Pay 100 today, receive 60 in each of the next
two years:
npv(0.1, {-100, 60, 60})
500/121
irr({-100, 60, 60})
0.130662386292
The investment earns 13.07%. (500/121 is epher's exact-fraction
display, 1.14, showing the value whose decimal repeats; dec(500/121)
spells it as 4.13223140496.) The decision rule: take the project when
npv at your hurdle rate is positive, that is, when irr beats the
hurdle. At a 15% hurdle this one fails:
npv(0.15, {-100, 60, 60})
-1300/529
A flow list whose entries never change sign has no meaningful rate;
irr reports a domain error for those.
Everyday one-liners. The compound annual growth rate of an
investment that went 1,000 → 2,400 in five years, and the real return
of a 7% nominal return against 2% inflation (the exact-fraction display
again; dec(5/102) is 0.0490196078431):
(2400/1000) ^ (1/5) - 1
0.191357898167
1.07 / 1.02 - 1
5/102
The script collection ships 42 ready-made finance scripts building on these ten functions, four folders: interest, investing, loans, and savings, each with a transcript verified against the engine (scripts.html lists them with one-line summaries).
2. The web app (PWA)
2.1 Opening it
The web app lives at:
https://epher.org/pwa/
No installation is needed. It works in any modern browser on a computer, phone, or tablet.
This guide is also built into the app: open Help → In-app user guide in the menu bar (tap ☰ on a phone) to read it inside the app, in the app's current language. Help → Constants opens the constants browser: every builtin constant in groups (Math, Astronomy, Physics, Chemistry), each with its value and a short description; tap one to insert its name into the entry field, and the search box narrows the list. Tap any example in the guide to load it into the entry field.
2.2 Your first calculation
- Click the text field (it is already focused when the page loads).
- Type an expression, for example
2 + 3 * 4. - Press Enter or click the = button.
The result appears in large text below the field. Everything from chapter 1 works here, including variables, functions, and scripts. A short single answer stays on that line. A longer answer - a script's transcript with several answers, a table, a long number - appears in the result pane, the pane that also shows graphs, with one answer per line; on a phone the pane slides into view on its own. A copy icon sits just left of the answer: press it and the answer's values land on the clipboard, one per line; the result pane's long answers copy the same way.
While you type a name, a suggestion list appears beneath the field: the
arrows move the highlight, Enter or Tab accepts, Esc closes,
and a click accepts without leaving the keyboard. Each suggestion carries
a short description of the function or constant. F1 shows the same
description for the word under the cursor in the hint bar above the
keypad. If the first thing you type into an empty field is an operator
(+ - * / ^ % !), epher inserts ans for you, so the line continues
from the previous answer.
The on-screen keypad types for you, exactly as if you had pressed the
keys. Its banks cover the whole language: 123 digits and operators,
trig, ƒ functions, nΣ numbers and statistics, data matrices,
lists, and strings (the bank that types [[1, 2], [3, 4]], {1, 2, 3}, and "text"), 0x conversions and bases, dist regression and
distribution families, $ finance, π∇ constants and graph commands,
and ☉ astronomy (the one bank that scrolls). Every function in this
guide is on a key somewhere. Turn on key hints (the ? button)
to caption every key, or rest on a key to read what it does.
The Settings menu (the gear icon, or ☰ → Settings on a phone)
holds three groups. Theme and Language do what their names
say. Results shapes how answers are shown: exact fractions (on by
default, so 1 / 3 displays as 1/3), the notation (Auto,
Scientific, or Engineering), and thousands separators. These are
display settings only; the values underneath stay ordinary numbers.
2.3 History
Every calculation is added to the history list beneath the result, so you can scroll back and see what you did. Newest entries appear at the top, and the trash icon to the left of the History heading empties it (in the terminal, Ctrl+L, or a click on the same icon). The history is kept while the page is open.
Each entry sits between thin rules: a single-line expression is one row, and a multi-line script is one entry showing all of its lines. Click an entry to load it back into the entry field and run it again.
2.4 Graphing
Type graph followed by an expression and press Enter:
graph x ^ 2
epher draws the curve y = f(x) from x = −10 to x = 10 beneath the input, on a grid with labelled axes. You can graph any expression, including your own functions:
def f(x) = x ^ 3
graph f(x)
Every graph line adds another curve to the same plot, each with its own
colour, and a legend naming them. The curves are all solid, so the
legend and the captions are what tell them apart without colour. graph clear empties
the plot, and a Clear graph button at the top of the graph pane does
the same for curves and 3D surfaces together. The TUI keeps the command in
its Graph menu.
At the top of the graph pane, beside Clear graph, Copy SVG, and Save PNG, the toolbar can hide the list of points of interest and the highlighted points drawn on the plot itself. Directly above every plot sits a strip of icon-labelled sliders, the words in each one's tooltip: line thickness (0 to 4 in steps of 0.1 for 2D curves, 0 to 0.4 in steps of 0.05 for 3D plots, with 0.2 the default - only the kind in view is shown, and each kind remembers its own value), plus zoom on every plot and, on 3D and solar plots, the horizontal and vertical rotation speeds. Pressing a slider's icon resets that slider to its default. Every legend entry has a checkbox, checked by default: unchecking a curve, a 3D surface, a space curve, or a solar body hides it from the plot, its points of interest, and the SVG export.
graph x ^ 2
graph x ^ 3
Points where the expression has no value (a division by zero, for example) are skipped, leaving a gap in the curve. A jump that is really a vertical asymptote is never drawn as a connecting line.
2.4.1 What you can plot
A domain of your choice:
graph sin(x) from 0 to 2*pi
Parametric curves, with t running from 0 to 2π:
graph param 2*cos(t), 3*sin(t)
Polar curves:
graph polar 1 + cos(theta)
Regions: y < shades the area below the curve, y > shades above:
graph y < x ^ 2
2.4.2 Reading the plot
Trace: move the pointer over the plot, or focus it and press the arrow keys. The nearest point on a curve is marked, with its coordinates announced beneath the plot.
Points of interest: after every graph command epher finds the roots and turning points of each curve and the intersections between curves, marks them on the plot, and lists them beneath it:
root (-1, 0) minimum (0, 0) root (1, 0)
Tables: the table command prints a table of values (rows where the
expression has no value are blank). An optional derivative <expr>
clause adds a third column, the numeric derivative of that expression
at each x:
table x ^ 2 from -2 to 2 points 5 derivative x ^ 2
x y y'
-2 4 -4
-1 1 -2
0 0 0
1 1 2
2 4 4
Table cells follow the Results settings: with exact fractions on (the
default), a value that is a simple fraction shows as one, table x / 3 from 0 to 1 points 4 lists 1/3 instead of 0.333.
table x ^ 2 from -2 to 2 points 5
x y
-2 4
-1 1
0 0
1 1
2 4
Two more clauses cover the rest of the work. values <list> takes the x column from a data list instead of an even grid: hand it your data and see it transformed:
d = {1, 2, 3, 4, 5}
table x ^ 2 values d
x y
1 1
2 4
3 9
4 16
5 25
And exact / approx force the cell display for one table: exact shows simple fractions wherever they fit, approx shows decimals; each overrides the Results settings for that one command.
2.4.3 Sliders and export
Define a constant, use it in a graph, and a slider appears beneath the plot. Drag it (or move it with the arrow keys) and every curve redraws:
const a = 1
graph a * x ^ 2
Every plot zooms the same way: scroll the mouse wheel over it (or pinch on a touch screen) to zoom in and out, and the zoom slider above the plot moves with you. Data plots - scatter, histogram, and box-and-whisker - zoom exactly like curves.
Copy SVG copies the current plot as a self-contained SVG image for pasting into documents. The colours are baked in and the background is transparent, so it sits on any page, slide, or document. Save PNG saves the same picture as a bitmap at twice its size, so curves stay crisp, with the same transparent background; the desktop app asks where to put it, the browser app saves it to your downloads (or asks, where the browser offers to). The slider rows and any animated constants sit directly beneath the plot, above the points-of-interest list. An animated slider is a real control: grab its indicator and drag it to a new value (grabbing stops the animation first), or nudge it with the arrow keys while it is focused.
2.4.4 3D surfaces
graph3d plots a surface z = f(x, y) over a square domain (−5 to 5, or
your from a to b):
graph3d x ^ 2 - y ^ 2
Mesh lines nearer to you are drawn stronger, so the shape reads in depth.
Several graph3d lines overlay, like curves, and graph3d clear empties
the plot. The pane shows one kind at a time: drawing a surface clears
the 2D curves, and drawing a curve clears the surfaces. Each plot
keeps its full size. Rotate the view by dragging, or focus the plot and use the arrow
keys. The terminal UI draws the same surface as an ASCII wireframe, with
the arrow keys rotating it.
Space curves plot with param: graph3d param cos(t), sin(t), t draws a helix over the t range you give (from 0 to 6.28318), or 0 to 2pi by default. Same pane, same rotation, zoom, and animation.
2.4.5 Animation
Every slider has a play button. It steps its constant through the slider's range and loops around: the indicator travels the full track and returns to the start when it reaches the end. This is the standard way calculators animate: you animate a parameter, and everything that uses it moves. Press the button again to pause.
A "time" variable is just a constant you animate:
const t = 0
graph sin(x - t)
Playing t's slider makes the wave travel. 3D surfaces animate the same way. Define a constant first, then play its slider:
const a = 1
graph3d sin(a * (x ^ 2 + y ^ 2)) from -3 to 3
In the terminal UI, the space bar starts and stops the animation.
2.5 Installing it and using it offline
The web app is a progressive web app: after one visit it works fully offline, and you can install it like a normal app.
- Chrome, Edge, or Android: click the install icon in the address bar (or Install app in the browser menu), then confirm.
- iPhone / iPad (Safari): tap Share → Add to Home Screen.
- Other browsers: look for Install or Add to Home Screen in the menu.
Once installed, launch it from your home screen or app list. It opens instantly, even with no internet connection.
2.6 What the web app does not do
The web app keeps your work to the current session: it evaluates expressions, graphs them (section 2.4), and keeps a history. The save, save script, and language commands work in the desktop, command line, and terminal versions (chapters 3, 4, and 5). In the web app they answer with a note that saving works there. Your recent activity is remembered - the history, your variables and functions, and the last answer survive closing the app, kept in this browser only.
3. The desktop app
The desktop app is a normal window around the same web app. Everything in chapter 2 applies; the difference is only how you install and start it.
3.1 Installing
Download one installer for your system from the epher website:
- Windows: run
epher-windows-x86_64.exe. The installer putsepheron your PATH. Open a new CMD or PowerShell window andepher "2 + 2"works. Because the build is not signed, choose More info → Run anyway on the first launch. - macOS: open
epher-macos-aarch64.dmgand drag epher into Applications. Because the build is not signed, macOS may block the first launch: allow the app via Open Anyway in System Settings → Privacy & Security. - Linux (Debian/Ubuntu): the
.debpackage
sudo apt install ./epher-linux-x86_64.deb
- Linux (Fedora/RHEL): the
.rpmpackage
sudo dnf install ./epher-linux-x86_64.rpm
- Linux (any distro, including Arch): the AppImage. Make it executable and run it:
chmod +x epher-linux-x86_64.AppImage
./epher-linux-x86_64.AppImage
Or install from a repository or store: updates then arrive with your package manager. On Debian/Ubuntu:
curl -fsSL https://epher.org/apt/KEY.gpg | sudo tee /etc/apt/keyrings/epher.gpg >/dev/null
curl -fsSL https://epher.org/epher.sources | sudo tee /etc/apt/sources.list.d/epher.sources >/dev/null
sudo apt update && sudo apt install epher
On Fedora:
curl -fsSL https://epher.org/epher.repo | sudo tee /etc/yum.repos.d/epher.repo >/dev/null
sudo dnf install epher
ARM64 builds of the .deb, .rpm, and AppImage are on the download page.
Every installer contains the whole epher: the desktop app, the command
line (chapter 4), and the terminal UI (chapter 5), as the single epher
command. On Linux, the package puts epher in /usr/bin.
3.2 Using it
Launch epher like any other application. You get a window with the same
interface as the web app: type an expression, press Enter or click
=, and read the result. Graphing works here too. graph x ^ 2 draws
in the window (chapter 2.4). The window can be window. The menu bar
includes Help → In-app user guide, the same guide as this page, with
tap-to-load examples.
You can also open it from a terminal: a bare epher (or epher gui) starts
the desktop app. On macOS, use the Install the epher command button inside
the app to put epher on your terminal PATH.
3.3 Storage: one store with the CLI and TUI
The desktop app shares its storage with the command line and terminal
versions. Functions, constants, scripts, history, and the language preference live in
one place, ~/.epher on your computer (or EPHER_STORE_DIR, chapter
4.6), and everything saved in one version is available in the others:
def area(w, h) = w * h
save area
Define area in the desktop app, save it, close the window. Then open
the CLI and area(3, 4) just works. It works the other way too: functions
and scripts you saved in the CLI or TUI are already there when the desktop
window opens, including variables set by saved scripts. The save,
save script, and language commands from chapter 4 work exactly the
same here.
Commands you type in the CLI, the REPL, the TUI, or the desktop app
all join the same history, and the session travels too: variables
you assign and the ans value follow you from one version to the
next. The shared store is live: while two versions are open at once,
a change in one is reflected in the other immediately (the desktop
app and the TUI watch the store and refresh in place).
The web app in the browser is the one version that does not use this storage. It keeps each session to itself (chapter 2.6).
4. The command line (CLI)
The CLI is the text side of the same epher program as the desktop app.
It has three modes: one-shot evaluation, piped scripts, and an interactive
session for longer work.
For help at any time, run epher --help (all commands, with
examples) or epher help (the full manual; on Linux packages this is the
man epher page).
Every installer carries the whole script collection with it - the same scripts the website's Scripts page browses - and installs it beside the program. The terminal commands you can copy on the website, in the scripts' README, and below all point at that installed folder, so they work as soon as epher is installed:
# Debian, Ubuntu, Fedora (deb, rpm)
epher /usr/lib/epher/scripts/astronomy/moon/full-moons.epher
# Windows (PowerShell)
epher "$env:LOCALAPPDATA\epher\scripts\astronomy\moon\full-moons.epher"
# macOS
epher /Applications/epher.app/Contents/Resources/scripts/astronomy/moon/full-moons.epher
4.1 One-shot calculations
Give the expression as an argument:
epher "2 + 3 * 4"
14
You can do anything from chapter 1 that is a single expression:
epher "if 3 > 2 then 10 else 20"
10
An expression that starts with a minus sign works directly:
epher "-2 + 5"
3
One-shot mode is for scripts, from a single expression up to a whole program. Each statement's value prints on its own line:
epher "x = 10; x + 5"
10
15
Statements joined with newlines work the same way inside the argument. Anything from chapter 1 is available: variables, functions, loops, everything. The lines share one session, like a piped script (section 4.2).
4.2 Piped scripts
epher - reads expressions from standard input, one line at a time, the way
scripting languages are used in pipelines:
# Linux and macOS (bash, zsh)
printf "x = 3\nx * 10\n" | epher -
# Windows PowerShell
"x = 3`nx * 10" | epher -
= 3
= 30
Everything from chapter 1 works, and the lines share one session: a function
defined on an early line is available later, and save writes to the same
store as always. Errors print and the script keeps going. A line may join
several statements with ;. Newlines and ; mean the same thing
everywhere in epher.
A file works the same way: epher plots/sine.es runs every line of the file in order and prints each result. The argument is treated as a file when it names an existing file and contains a ., / or \ - so epher x still evaluates the name x.
4.3 The interactive session (REPL)
Start it with epher repl:
epher repl
A bare
epherwith no arguments opens the desktop app (chapter 3).
epher prints its prompt and waits:
epher>
Now type anything from chapter 1, one line at a time. Variables keep their values between lines:
epher> x = 5
= 5
epher> x ^ 2
= 25
The table command (section 2.4.2) prints a table of values here too:
epher> table x ^ 2 from -2 to 2 points 5
x y
-2 4
-1 1
0 0
`graph` lines plot here too: the curves build up across lines, and
`graph save plot.svg` writes the same SVG image the web app's
**Copy SVG** button yields. `graph3d save file.svg` saves a 3D surface
the same way. The same graph lines work in one-shot and piped scripts:
`epher "graph sin(x); graph save plot.svg"` is a complete plot in one
command.
1 1
2 4
Each answer is shown as = result. To leave, type quit (or exit):
epher> quit
Your history is remembered: the next time you run epher repl, the previous
session's lines are still there.
The load command runs a script - a file path, or the name of a script you saved with save script - line by line, exactly as if you had typed it:
epher> load plots/sine.es
epher> load my_setup
4.4 Saving functions, constants and scripts
Define a function, then save it:
epher> def fib(n) = if n <= 1 then n else fib(n - 1) + fib(n - 2)
epher> save fib
saved fib
The save fib command stores the function on disk. Next time you start the
session, fib is already defined:
epher> fib(10)
= 55
Constants save the same way. save on the constant's name:
epher> const tax = 0.2
= 0.2
epher> save tax
saved tax
To save a whole script (the last line you typed) use save script:
epher> x = 0; while x < 5 do x = x + 1; x
= 5
epher> save script count_to_five
saved script count_to_five
Saved scripts run automatically when epher starts, so anything they define is ready for you.
You can also load a saved script on demand with load count_to_five, or keep it as a plain file and run load count_to_five.es; epher count_to_five.es runs it straight from the command line (section 4.2).
4.5 Changing the interface language
The interface language is chosen from the languages you set on your device.
To override it, type language followed by one of: en, zh-CN, hi,
es, fr, ar, de, pt:
epher> language fr
language set to fr
The choice is remembered for next time. Note: the language you type, the expression language, is always the same, in any interface language.
4.6 Where your data lives
Functions, scripts, history, and your language choice are stored in one folder on your computer:
~/.epher
Delete that folder to start completely fresh. To use a different location,
set the environment variable EPHER_STORE_DIR before starting epher:
EPHER_STORE_DIR=/tmp/my-epher epher repl
5. The terminal UI (TUI)
The TUI is a full-screen version of the interactive session, inside your
terminal. It is part of the same epher program. Start it with:
epher tui
5.1 The screen
The screen is divided into panels:
- Expression: the input box (top). Shift+Enter starts a new line, and the arrow keys or a mouse click move the cursor inside the text.
- The current result right below it.
- History: every line you entered, with its answer.
- Graph: the plot from the
graphcommand (bottom). - A hint line shows the keyboard shortcuts.
5.2 Keys
| Key | Action |
|---|---|
| Type | add to the expression at the cursor |
| Enter | evaluate the whole script (a multi-line entry runs as one history item) |
| Shift+Enter | start a new line |
| ← → ↑ ↓ | move the cursor (with empty input: rotate the 3D graph) |
| Esc | clear the input line |
| F1 | describe the function under the cursor (in the answer line) |
| Ctrl+C | quit |
| q | quit (when the input is empty) |
| Arrow keys | rotate the 3D view (when the input is empty) |
| Space | start/stop the animation (when the input is empty) |
| F9 | open the menus (File, Edit, Graph, Settings, Help) |
| Tab | focus the always-visible keypad (or history, from the keypad); switch its banks (Esc returns focus to typing) |
| Mouse | click menus and popup items, keypad cells and bank tabs, and history lines (loads the expression); drag the graph panel to orbit (3D) or pan (2D), the wheel zooms, a double-click resets the view |
| Ctrl+L | clear the history |
The Help menu opens the in-app guide, the keypad key help, and a constants browser: the builtin constants in groups, arrows choose a row, Enter inserts its name into the expression at the cursor, and Esc closes.
The keypad's banks hold every function, constant, and command the
language supports, in the web keypad's tab order: trig, fn,
num, data, dist, fin, 0x, var, and astro
(the digits bank is 123). The
0x bank holds the exactness and base conversions (frac, dec,
big, bin, oct, hex) and the factorial !. Arrow keys
move the highlight, Enter inserts the token, and Tab cycles
the banks. An operator typed into an empty line (or inserted from the
keypad) adds ans first, so the line continues from the previous
answer.
The Settings menu offers the same result display choices as the web app (exact fractions, notation, thousands separators), next to the theme and language rows.
5.3 Graphing
Type graph followed by an expression, and press Enter:
graph x ^ 2
epher samples the curve from x = −10 to x = 10 and draws it as an ASCII plot in the Graph panel; the legend above the plot names what is plotted.
graph clear empties the plot, and the Graph menu does the same; the
Help menu opens this guide inside the TUI (arrow keys scroll, Esc
closes). The Settings menu can hide the points of
interest listed under the plot.
You can graph any expression, including your own functions. First define one, then graph it:
def f(x) = x ^ 3
graph f(x)
Every graph line adds a curve to the plot, drawn with its own symbol
(o, x, +, *); graph clear empties the plot. The same grammar as
the web app applies: a domain (graph sin(x) from 0 to 2*pi), parametric
curves (graph param 2*cos(t), 3*sin(t)), polar curves
(graph polar 1 + cos(theta)), and regions (graph y < x ^ 2 shades the
area below the curve).
Points where the expression has no value (for example division by zero)
are simply skipped, leaving a gap in the plot. After every graph command
the TUI lists the points of interest (roots, turning points, and
intersections) under the plot. The table command (section 2.4.2) works
here too.
graph3d x ^ 2 - y ^ 2 plots a 3D surface as an ASCII wireframe.
Rotate it with the arrow keys while the input is empty, and press the
space bar to animate a slider constant (section 2.4.5). The bottom hint
line shows the arrow-key and space hints only while a 3D surface or an
animatable curve is displayed.
graph save plot.svg writes the current plot as the same SVG image the
web app's Copy SVG button yields; graph3d save file.svg saves the
3D wireframe from the angle you are looking at.
5.4 Saving and persistence
The TUI shares its storage with the CLI: everything saved in one is
available in the other. Functions, scripts, history, and the language
preference live in ~/.epher (chapter 4.6), and the same save,
save script, and language commands work here.
6. Your data and privacy
- The installed epher program (desktop app, CLI, and TUI) stores
functions, scripts, history, and the language choice locally in
~/.epher(orEPHER_STORE_DIR). Nothing leaves your computer. - The web app keeps nothing on disk: history lasts only while the page is open. The web app can work offline because the page itself is stored by your browser.
All five versions run the calculation entirely on your device. Nothing is sent anywhere.