MathPad is an algebraic equation solver. Enter variables and equations, and MathPad automatically detects unknowns and solves using root-finding. Solve forward or backward — give it any combination of knowns and unknowns.
MathPad organizes work into records, each with a formulas editor and a variables panel. Use the sidebar to create, rename, and switch between records. Open multiple records as tabs.
For a hands-on walkthrough, open the records in the Tutorial category in the sidebar — they cover the language and the app step by step, from a first equation through tables, dates, and the workflow tools.
The sidebar lists every record, grouped by category. On narrower screens it's hidden behind the hamburger menu (☰) in the header.
A↓ next to the header toggles between insertion order and alphabetical sort.✕ appears only on empty categories (other than Unfiled and Reference) and deletes them.Single-click a record in the sidebar to open it as a preview tab (italicized, single slot — opening another preview replaces it). A preview is promoted to a persistent tab when you edit the record, rename it, change a setting, or double-click it. Persistent tabs survive reloads; previews do not.
Variables are declared with a name followed by a marker that determines their behavior:
| Syntax | Type | Behavior |
|---|---|---|
var: value | Input | Persistent — keeps value across Solve and Clear |
var<- value | Input | Cleared by the Clear button |
var<<- value | Input | Full precision, cleared by Clear |
var:: value | Input | Full precision, persistent |
var-> | Output | Cleared and recomputed on Solve |
var->> | Output | Full precision, cleared on Solve |
var:> | Output | Persistent output — recomputed on Solve, kept on Clear |
var:>> | Output | Persistent output, full precision |
var[lo:hi]: | Input | Constrain root-finding search range |
var[lo:hi:step]: | Input | With explicit step for Brent's grid scan |
::, <<-, ->>, :>>) store and display values with maximum precision instead of rounding to the record's decimal places setting.
[50:0] = [0:50]). Limit expressions can reference other variables (y[0:x*2]<-); the solver defers a variable until its limit dependencies are solved. The ° format marker is display-only (not a solver signal). For a search that wraps through 0°, write a linear range extending past 360 — e.g., cmg[327.8:365.5]° covers the 37.7° arc. Trig is periodic so Brent’s evaluating 365° equals evaluating 5°.
Input markers (:, <-, <<-, ::) define variables you provide values for. Output markers (->, ->>, :>, :>>) define variables that MathPad computes. Output variables are cleared before solving so the solver can fill them in.
A variable whose value is an expression (not just a number) acts as a definition:
area: length * width circumference: 2 * pi * radius
Definitions are evaluated in dependency order — out-of-order and chained dependencies are handled automatically.
An equation is any line with = between two expressions:
distance = speed * time a**2 + b**2 = c**2
If all variables have values, MathPad checks that both sides balance. If there's an unknown, it solves using Brent's root-finding method.
For systems of equations with multiple unknowns, the solver algebraically rearranges each equation to isolate every possible variable (both directions — x = z/2 yields x and z substitutions), then uses a recursive backtracking search. It tries small combinations of substitutions first, letting Brent's find one-unknown reductions; if a branch doesn't balance, it backtracks and tries another. Declaring answer variables with a range hint (x[low:high]<-) helps pick the desired root when multiple valid solutions exist.
Use =° for mod-aware comparison (mod 360 in degrees mode, mod 2π in radians):
heading =° target_heading
This correctly handles wraparound — e.g., 359° and 1° are only 2° apart. Also works as a logical operator in expressions, returning 1 (true) or 0 (false).
Wrap long equations in curly braces:
{long_expression_a + long_expression_b
= long_expression_c + long_expression_d}
| Format | Example |
|---|---|
| Decimal | 123, 3.14, 1.5e-10 |
| Base literal | ff#16 (hex), 1010#2 (binary), 77#8 (octal) |
| Percent literal | 5% (stored as 0.05) |
| Degree literal | 400° (stored as 400, no mod) |
In Float format, the decimal-places setting is a minimum precision for small numbers, not a hard cap. A value below 1 is shown with at least one more significant figure than your places setting, so it isn't crushed to a couple of digits — at 3 places, 0.0284130625 displays as 0.02841, not 0.028. This keeps small results precise enough to stay consistent with larger related values, so an equation and its algebraic rearrangement both balance.
If a number has more leading zeros than your places setting, it switches to scientific notation instead of a long run of zeros — at 3 places, 3.14×10⁻¹⁴ shows as 3.142e-14 rather than 0.0000000000000314. Full-precision outputs (->>) follow the same rules, showing every significant digit the value carries.
Add a format prefix before the marker to control display:
| Prefix | Example | Display |
|---|---|---|
$ | price $: 1234.5 | $1,234.50 (always 2 decimals, comma grouping). The currency symbol is configurable in record settings. |
% | rate %<- 7.5% | 7.5% (stored as 0.075) |
° | angle °: 400 | 40° (degrees mode: mod 360 with °; radians mode: mod 2π without symbol) |
@d | when @d: 4/1/2026 | 04/01/2026 (date, locale format) |
@t | dur @t: 1:30:00 | 1:30:00 (duration H:MM:SS) |
#16 | hex #16-> 255 | FF (bases 2-36) |
Format suffixes also work on expression outputs:
total cost: qty * unit_price (total cost)$-> (rate)%-> (heading)°-> (Now)@d->
Listed by precedence (highest to lowest):
| Operator | Description |
|---|---|
- + ~ ! | Unary: negate, plus, bitwise NOT, logical NOT |
** | Power (right-associative) |
* / | Multiply, Divide |
+ - | Add, Subtract |
<< >> | Bit shift left, right |
& | Bitwise AND |
| ^ | Bitwise OR, XOR |
== != < <= > >= | Comparison (returns 1 or 0) |
&& | Logical AND |
|| ^^ | Logical OR, XOR |
Function arguments are separated by semicolons: f(a; b; c)
| Function | Description |
|---|---|
Abs(x) | Absolute value |
Sign(x) | -1, 0, or 1 |
Int(x) | Integer part (truncate toward zero) |
Frac(x) | Fractional part |
Round(x) | Round to nearest integer |
Floor(x) | Round down |
Ceil(x) | Round up |
Mod(a; b) | Remainder (always non-negative for positive b) |
Sqrt(x) | Square root |
Cbrt(x) | Cube root |
Root(x; n) | Nth root |
Exp(x) | e raised to x |
Ln(x) | Natural logarithm |
Log(x) | Base-10 logarithm |
Fact(n) | Factorial (n!) |
Pi | 3.14159... |
Rand | Random number 0 to 1 |
Uses degrees or radians depending on the record's Degrees mode setting.
| Function | Description |
|---|---|
Sin(x), Cos(x), Tan(x) | Trigonometric functions |
ASin(x), ACos(x), ATan(x) | Inverse trig |
SinH(x), CosH(x), TanH(x) | Hyperbolic |
ASinH(x), ACosH(x), ATanH(x) | Inverse hyperbolic |
Radians(deg) | Convert degrees to radians |
Degrees(rad) | Convert radians to degrees |
Dates are stored as epoch seconds (seconds since Jan 1, 1970 UTC). Use @d to format as a date and @t for durations:
@d-> — date only (locale format)@d->> — date and time (HH:MM:SS)@d: — input accepts date text (e.g. 4/1/2026 14:30)@t-> — duration as H:MM:SS@t->> — duration with fractional seconds@t: — input accepts duration text (e.g. 1:30:00)Durations of 24h or more display with a day count (2d 5:30:00 is 2 days plus 5 hours 30 minutes); the parser accepts the same form on input.
| Function | Description |
|---|---|
Now | Current date/time as epoch seconds |
Date(y; m; d [; h; min; s]) | Create date as epoch seconds |
Days(d1; d2) | Days between two dates |
Year(d), Month(d), Day(d) | Extract date components |
Weekday(d) | Day of week (1=Sunday, 7=Saturday) |
Hour(d), Minute(d), Second(d) | Extract time components |
Hours(d) | Decimal hours of day from date |
TimePart(d) | Seconds since midnight (for @t display of time-of-day) |
Example: start @d: 4/1/2026 14:30 — Days(start; Now)@t-> shows elapsed time
| Function | Description |
|---|---|
If(cond; then; else) | Conditional — returns then if cond is non-zero, else otherwise |
Choose(n; v1; v2; ...) | Returns the nth value (1-based) |
Min(a; b; ...) | Minimum of values |
Max(a; b; ...) | Maximum of values |
Avg(a; b; ...) | Average of values |
Sum(a; b; ...) | Sum of values |
Sum or multiply an expression over an integer range. The second argument is the index variable's name; the index runs from start to end inclusive and is in scope only inside expr.
| Function | Description |
|---|---|
Sum(expr; var; start; end) | Sum of expr evaluated with var = start..end |
Prod(expr; var; start; end) | Product of expr evaluated with var = start..end |
Sum(a; b; ...) | Variadic form — sum of fixed arguments |
Avg(a; b; ...) | Average of fixed arguments (no binding form) |
Example: Sum(1/k**2; k; 1; 1000) — partial sum of the Basel series
| Function | Description |
|---|---|
isClose(a; b; places) | 1 if a and b are equal within tolerance, 0 otherwise |
modIsClose(a; b; n; places) | Like isClose but mod-aware (equal modulo n) — useful with °= angles, e.g. modIsClose(a; b; Perigon; Places) |
Tau | 2π (always) |
Perigon | 360 in degrees mode, 2π in radians mode |
Places | Current record's decimal places setting |
For financial helpers like pmt and compound, see the Default Functions record below.
Define functions in any record using the syntax:
f(x; y) = expression using x and y
Define functions in the special "Functions" record to make them available to all records. Function parameters use semicolons as separators, just like built-in functions.
In the formulas editor, a definition's signature — the name(arg1; arg2; …) part up to the = — is shown with a subtle highlighted band, so a function definition stands out from an ordinary equation at a glance.
Function bodies can reference values from the Constants record and call other user-defined functions and built-ins. They cannot read variables from the calling record — only the function's parameters and the global environment (constants + functions) are visible inside the body. Shadowing a constant locally (e.g. c: 1 in a record that uses a function referencing c) does not change the function's behavior — it always sees the canonical value from the Constants record. This keeps functions self-contained and predictable.
A fresh MathPad ships with these functions already defined in the Functions record — available everywhere unless you edit or remove them:
| Function | Description |
|---|---|
pmt(pv; rate; n; fv) | Loan / annuity payment (returns a negative value for cash flowing out) |
compound(pv; rate; n) | Compound-interest future value: pv * (1 + rate)**n |
ctof(c) / ftoc(f) | Celsius ↔ Fahrenheit conversion |
hypot(a; b) | sqrt(a**2 + b**2) |
disc(a; b; c) | Quadratic discriminant b**2 - 4*a*c |
Edit the Functions record to add, remove, or modify these.
| Name | Purpose |
|---|---|
| Constants | Variables defined here are available in all other records |
| Functions | User-defined functions available to all records |
| Default Settings | Settings template for newly created records |
The variables panel (above the formulas editor) shows a structured view of all variables:
⟲ button next to any editable variable clears its value and re-solves — a quick way to ask "what should this variable be?" without manually clearing first. Ctrl+click adds trace output; Shift+click appends the table-outputs text section.Add a --Variables-- line in the formulas editor. Only lines below this marker appear in the variables panel. Lines above it (formulas, equations) are hidden from the panel.
// are hiddenText in double quotes is treated as a comment and ignored by the solver:
"This is a comment" x: 42 "the answer"
Comments after a variable declaration appear in the variables panel.
Access a variable's value from before the current solve started:
| Syntax | Description |
|---|---|
x~ | Pre-solve value of x (error if none) |
x~? | 1 if x has a pre-solve value, 0 otherwise |
Example: counter: if(counter~?; counter~ + 1; 0) — increments each time you solve
Inside a table, grid, or vectorDraw body, ~ refers to the value from the PREVIOUS ROW (or cell), not the previous solve. The first row sees no pre-solve values (so x~? is 0); subsequent rows inherit from the previous row. This is the foundation of running-total patterns:
table("Investment growth") = {
year: 1..10
balance = if(balance~?; balance~; 0) * (1 + rate) + contrib
...
}
Use table to iterate a variable over a range and display results in columns. Use tableGraph to render those rows as an SVG line graph. Use grid for 2D cell grids with two iterators. Use vectorDraw to render SVG vector diagrams (navigation, polar, or cartesian).
table("Distance vs Time") = {
distance = speed * time
speed: 60
time<- 1..5
time->
distance->
}
Multiple iterators iterate as nested loops over the cartesian product. The first-declared iterator is the outermost loop (changes slowest); the last-declared is innermost (changes fastest). Iterator bounds are evaluated once up-front, so inner iterators cannot depend on outer iterator values.
Same body syntax as table, but rendered as an SVG line graph. Column 0 is the X-axis; remaining columns are Y series (one line each).
With multiple iterators, line grouping is opt-in: an inner iterator becomes a line-grouping variable only if it has a iter-> output column. The column's label is used in the legend (e.g. Y y-> produces Y = 1.0). Without y->, the inner iterator just sweeps silently and only one line is drawn.
tableGraph("z = x^y") = {
z = x**y
x: 1..2..0.1
y: 1..2..0.1
X x->
Y y->
Z z->
}
The above renders 11 lines (one per y value), each plotting z against x, labeled Y = 1.0, Y = 1.1, … Y = 2.0.
Hover the mouse over a graph to get a crosshair and a readout of the (x, y) coordinates at the pointer.
grid("Multiplication") = {
z = x * y
z<-
x<- 1..5
y<- 1..5
x->
y->
z->
}
Wrap an expression in backslashes inside the title string to interpolate its current value:
table("Loan at \rate%\ over \years\ years") = { ... }
The expression is evaluated against the record's variables and re-rendered each Solve. Useful for self-labeling tables and graphs whose subject depends on the inputs.
| Syntax | Role | Description |
|---|---|---|
x<- 0..10 | Iterator | Range from 0 to 10, step 1 |
x: 0..10..2 | Iterator | Range with step 2 |
z<- or z: | Unknown | Bare declaration — solved by equations |
z[lo:hi]<- | Unknown with limits | Constrain search range |
v: 10 | Definition | Expression value set per row |
Label z-> | Output | Output column with optional label |
Include equations inside the body. If the body has no equations, the table inherits equations from the surrounding record. If the body has any equations, only those are used.
An optional font size can be specified as a second argument:
table("Title"; 12) = {
...
}
Click a table title to collapse or expand it.
Use vectorDraw to render SVG vector diagrams. The second argument is the coordinate type (required) and selects how each vector's four outputs are interpreted:
| Type | Pair semantics | Convention |
|---|---|---|
navigation | (direction, magnitude) | 0° = up (north), positive angles clockwise — compass bearings |
polar | (direction, magnitude) | 0° = right (east), positive angles counter-clockwise — math convention |
cartesian | (x, y) | raw coordinates — no angle handling |
Each vector is defined by four outputs: a start pair (absolute position from the origin) and an end pair. For navigation and polar the end pair is the relative displacement — (direction, magnitude) added to the start. For cartesian the end pair is the absolute destination point (x, y).
vectorDraw("Wind Triangle"; navigation) = {
"equations..."
tc °-> "start direction (bearing)"
start_mag -> "start magnitude"
Label end_dir °-> "end direction (labels the vector in the legend)"
end_mag -> "end magnitude (relative displacement)"
}
Labels on the end pair identify the vector in the legend. Direction columns use the record's degrees/radians mode (cartesian columns are unitless). Collapsible titles and optional font size work just like tables — for vectorDraw the font size is the third argument: vectorDraw("Title"; polar; 12).
The legend and all vector outputs respect the record's decimal places, strip zeros, and group digits settings.
When a table, grid, or vector diagram doesn't fully solve, its title shows (n/m solved) indicating how many rows, cells, or unknowns were successfully solved. This indicator is hidden when everything solves.
.json data file (a complete backup that re-imports exactly and matches the Google Drive format), or the .txt text/PalmOS format..txt export file, or from a MathPad .json data file (for example one downloaded from your Google Drive). Import REPLACES all existing records, so export first if you want to keep them..txt format is also accepted by the original 1997 PalmOS MathPad's MpExport utility, so old PalmOS archives can be imported as-is.~~~~~~~~~~~~~~~~~~~~~~~~~~~); each block starts with metadata (Category, Places, Format, etc.) and is followed by the record's raw text. The file is plain text — open it in any editor to trim, fix, or stitch together exports.Access Import/Export from the sidebar buttons.
Optional cloud sync keeps all your records on Google Drive and across your devices. Use the Drive control in the header (the cloud / your initial) to sign in. Without it, MathPad still works entirely in your browser — Drive is purely additive.
MathPad.json, in a MathPad folder on your Drive, and every device syncs to it.MathPad.json is plain text you can open, download, or back up yourself.If this device's records and the Drive file differ (e.g. you edited on another device), MathPad asks how to combine them — never overwriting silently:
.json is added automatically); saving over an existing name asks before overwriting.Cmd substitutes for Ctrl on Mac.
| Shortcut | Action |
|---|---|
Ctrl+Enter / Ctrl+S | Solve current record |
Ctrl+Shift+Enter | Solve and append a --- Table Outputs --- text section |
Ctrl+Shift+S | Clear inputs and outputs (same as the Clear button) |
Ctrl+Z | Undo (routes to the active editor; also undoes Solves) |
Ctrl+Y / Ctrl+Shift+Z | Redo |
Tab (in formulas) | Indent line by 2 spaces (or selection) |
Shift+Tab (in formulas) | Outdent line by 2 spaces |
Ctrl+/ (in formulas) | Toggle // line comment on selection |
Tab / Shift+Tab (in vars panel) | Commit edit, Solve, and cycle to the next / previous input |
Enter (in vars panel) | Commit edit and Solve |
Escape | Revert edited variable value, unfocus formulas, close modals, or stop a running solve |
Hold modifier keys when you click the Solve button:
| Click | Action |
|---|---|
| Solve | Normal solve |
| Shift+click | Solve, then append a --- Table Outputs --- text section |
| Ctrl+click | Solve in trace mode — appends a *--- Solve Trace --- section showing the steps the solver took. Useful when a result surprises you. |
| Ctrl+Shift+click | Trace plus table outputs |
While a solve is running, the Solve button turns red and reads Stop — click it (or press Escape) to cancel. Editing the record, or switching to another record, also cancels a running solve. Most solves finish instantly, so you'll usually only see this on a large table or a heavy computation.
Click the gear icon to configure per-record settings:
| Setting | Description |
|---|---|
| Category | Organize records into collapsible groups in the sidebar |
| Decimal Places | Number of decimal places for output (default 2) |
| Format | Float, Scientific, or Engineering notation |
| Currency | Symbol for $ format ($ € £ ¥ ₹ ₩ ₱ ₺ ₴ ₫ ₡ ₽ ₸ ₼ ₾ ৳) |
| Strip Zeros | Remove trailing zeros from output |
| Group Digits | Add comma separators (1,234,567) |
| Degrees Mode | Trig functions use degrees instead of radians; ° format displays mod 360 with ° suffix (radians mode displays mod 2π without symbol) |
The settings panel also shows the record's Created and Modified timestamps. Modified updates only when you edit the formulas text directly — not when you solve, clear, or change variable values in the variables panel.
"Loan calculator" --Functions-- pmt(pv; rate; n; fv) = -(pv + fv / (1 + rate)**n) * rate / (1 - (1 + rate)**-n) --Equations-- pmt = pmt(pv; rate/12; years*12; fv) --Variables-- pv $<- $100,000 "present value (loan or annuity)" fv $<- $0 "future value (balloon payment)" rate %<- 6.125% "annual interest rate %" years <- 30 "number of years" pmt $<- "monthly payment"
How it works:
--Functions-- and --Equations-- are visual labels — only --Variables-- is a real section marker (controls what shows in the variables panel)$ formats as money, % as percentpmt: -$607.61pmt: -$2,000 and clear pv, then solve — MathPad finds pv: $329,158.21