User Guide
Complete guide to CalcMark features, editor shortcuts, and workflows.
Contents #
- Editor Shortcuts – Keyboard shortcuts for the CalcMark editor
- Locale Formatting – Display numbers in your locale
- Exporting Results –
cm convertandcm eval - Sharing with GitHub Gist – Share and open documents via GitHub Gist
- Language Features
- Supported Units – Physical, data, and currency units
- Unit Conversion –
inandaskeywords - Currency Conversion – Exchange rates in frontmatter
- Global Variables – Reusable values in frontmatter
- @Directive References –
@scaleand@globals.namein expressions - Scale and Convert – Document-wide scaling and unit system conversion
- Built-in Functions
- Function Reference – Detailed examples for every function
- Natural Language Syntax – NL forms reference table
- Network Functions – RTT, throughput, transfer time, downtime
- Storage Functions – Read, seek, compress
- Growth Functions – Compound, linear, and depreciation
- Rates and
over– Rate literals, accumulation, and conversion - Date Arithmetic – Date literals, durations, and
from - Napkin Math – Quick estimates with
as napkin - Precise Display – Full precision with
as precise - Capacity Planning –
at...persyntax - Multiplier Suffixes – K, M, B shortcuts
- Percentages – Percentage calculations
- Worked Examples – Complete calculation scenarios
- Tips – Organize with markdown, preview pane, getting help
- Troubleshooting – Common errors and fixes
Editor Shortcuts #
The CalcMark editor provides keyboard shortcuts for common actions. Press F1 for full help inside the editor.
File #
| Shortcut | Action |
|---|---|
| Ctrl+N | New empty document |
| Ctrl+S | Save document |
| Ctrl+O | Open file |
| Ctrl+T | Export to format |
| Ctrl+Q | Quit editor |
Edit #
| Shortcut | Action |
|---|---|
| Ctrl+Z | Undo last change |
| Ctrl+Y | Redo last change |
| Ctrl+K | Delete current line |
| Ctrl+F | Add YAML frontmatter |
| Ctrl+A | Select all |
| Ctrl+C | Copy selection |
| Ctrl+X | Cut selection |
| Ctrl+V | Paste |
| Ctrl+Backspace | Delete word |
View #
| Shortcut | Action |
|---|---|
| Ctrl+P | Cycle preview mode |
| Ctrl+H / F1 | Open command menu |
Navigation #
| Shortcut | Action |
|---|---|
| Opt+Left / Opt+B | Move to previous word |
| Opt+Right / Opt+F | Move to next word |
| Ctrl+Home | Jump to document start |
| Ctrl+End | Jump to document end |
| Ctrl+Left / Ctrl+Right | Move word left/right |
| Ctrl+D | Scroll down half page |
| Ctrl+U | Scroll up half page |
| Shift+Arrow | Extend selection |
Locale Formatting #
CalcMark can format output numbers using locale-specific decimal and thousands separators. This affects how results are displayed in all output modes (editor, REPL, eval, convert).
Setting Your Locale #
Per-command with the --locale flag:
cm eval --locale=de-DE budget.cm
cm convert doc.cm --to=html --locale=fr-FR
Permanently in your config file:
# ~/.config/calcmark/config.toml
locale = "de-DE"
The --locale flag overrides the config file for that invocation.
Example: Same Document, Different Locales #
Given a CalcMark document:
price = $1500
pi = 3.14159
users = 1500000
weight = 50.5 kg| price = $1500 | → | $1,500.00 |
| pi = 3.14159 | → | 3.14159 |
| users = 1500000 | → | 1.5M |
| weight = 50.5 kg | → | 50.5 kg |
| Value | en-US (default) | de-DE | fr-FR |
|---|---|---|---|
price | $1,500.00 | $1.500,00 | $1 500,00 |
pi | 3.14159 | 3,14159 | 3,14159 |
users | 1.5M | 1,5M | 1,5M |
weight | 50.5 kg | 50,5 kg | 50,5 kg |
What Stays the Same #
Regardless of locale, these never change:
- Input syntax – always use
.for decimals and,or_for thousands in your source - K/M/B/T suffixes – always English letters
- Currency symbols – always prefix position (
$,€, etc.) - CalcMark format –
cm convert --to=cmis always locale-independent
JSON Output #
When exporting to JSON, each result includes a locale-formatted display value and structured type information:
cm convert budget.cm --to=json --locale=de-DE
{
"source": "price = $1500",
"value": "$1.500,00",
"type": "currency",
"numeric_value": 1500,
"unit": "USD",
"variable": "price"
}
Use type for dispatch, numeric_value + unit for computation, and value for display. The type, numeric_value, and unit fields are always locale-independent. See Configuration: JSON Output for details.
Exporting Results #
Convert CalcMark files to other formats using cm convert:
cm convert doc.cm --to=html # HTML to stdout
cm convert doc.cm --to=md -o doc.md # Markdown file
cm convert doc.cm --to=json # JSON to stdout
cm convert doc.cm --to=html -T tpl.html # Custom HTML template
Use cm eval for quick evaluation, or pipe directly into cm:
cm eval budget.cm # Print final results
cm eval -v budget.cm # Show all intermediate values
echo "1 + 2" | cm # Evaluate piped input (auto-detects pipe)
echo "1 + 2" | cm --format json # JSON output for scripting/agents
cm eval --locale=de-DE budget.cm # German number formatting
All export formats respect the --locale flag (or config setting) except cm format, which stays locale-independent.
Sharing with GitHub Gist #
Share CalcMark documents as GitHub Gists directly from the editor. This feature requires the GitHub CLI (gh) to be installed and authenticated.
Prerequisites #
Install and authenticate the GitHub CLI:
# Install
brew install gh # macOS
sudo apt install gh # Debian/Ubuntu
# Authenticate
gh auth login
Share To Gist #
Open the command menu (Ctrl+H or F1), select Share To Gist, then choose visibility (public or secret) and add an optional description. Press Enter to share. The Gist URL is copied to your clipboard.
If you are not authenticated, CalcMark will launch gh auth login interactively and retry after you sign in.
Open From Gist #
Open the command menu, select Open From Gist, then paste a Gist URL or ID. CalcMark fetches the Gist content and loads it into the editor. If the Gist contains multiple files, .cm files are preferred.
Note: Sharing with GitHub Gist is not available in the browser (WASM) build.
Language Features #
Supported Units #
CalcMark supports a wide range of units across categories:
- Area: cm², m², km², ha, in², ft², yd², mi², acre
- DataSize: byte, KB, MB, GB, TB, PB (and binary: KiB, MiB, GiB, TiB)
- Energy: J, kJ, cal, kcal, kWh
- Length: m, cm, mm, km, in, ft, yd, mi, nmi (nautical mile)
- Mass: mg, g, kg, metric ton (t), oz, lb
- Power: W, kW, MW, hp
- Speed: m/s, km/h, mph, knot
- Temperature: C, F, K
- Volume: mL, L, tsp, tbsp, cup, pt, qt, gal
Time units (second, minute, hour, day, week, month, year) are used in durations and rates but are not a conversion category.
Run cm help constants for the complete list with aliases and descriptions.
Unit Conversion #
Convert between compatible units using in or as:
distance = 5 miles
distance_km = distance in km
temp_c = 20 celsius
temp_f = temp_c in fahrenheit
file_size = 1.5 GB
file_size_mb = file_size in MB| distance = 5 miles | → | 5 mi |
| distance_km = distance in km | → | 8.05 km |
| temp_c = 20 celsius | → | 20 celsius |
| temp_f = temp_c in fahrenheit | → | 68 fahrenheit |
| file_size = 1.5 GB | → | 1.5 GB |
| file_size_mb = file_size in MB | → | 1,536 MB |
Currency Conversion #
Convert between currencies using in with exchange rates defined in YAML frontmatter:
---
exchange:
USD_EUR: 0.92
EUR_GBP: 0.86
---
price_usd = $100
price_eur = price_usd in EUR
salary = 50000 EUR
salary_gbp = salary in GBPline 2: no exchange rate defined for USD → EUR; add to frontmatter: exchange:
USD_EUR: <rate>Exchange rates use the format FROM_TO: rate where 1 unit of FROM equals rate units of TO.
Global Variables #
Define reusable values in the frontmatter that can be referenced throughout your document:
---
globals:
base_date: Jan 15 2025
tax_rate: 0.32
base_price: $100
sprint_length: 2 weeks
bandwidth: 100 MB/s
---
Reference globals in expressions with the @globals. prefix:
net_price = @globals.base_price * (1 - @globals.tax_rate)
project_end = @globals.base_date + @globals.sprint_length * 6
monthly_transfer = @globals.bandwidth over 30 days
Globals support all CalcMark literal types:
- Numbers:
42,3.14,1.5K,25% - Quantities:
10 meters,5 kg,100 MB - Currencies:
$100,50 EUR - Dates:
Jan 15 2025 - Durations:
5 days,2 weeks - Rates:
100 MB/s,$50/hour - Booleans:
true,false
Globals must be literal values. Expressions like 1 + 1 are not allowed.
@Directive References #
Use @scale and @globals.name to reference frontmatter values in expressions:
per_unit = total_cost / @scale
tax = income * @globals.tax_rate
@scale resolves to the numeric scale factor from frontmatter. @globals.name resolves to the typed value of a named global. These are the only supported directives — @exchange, @convert_to, and other names produce errors.
@globals without a field name (e.g., bare @globals) is a parser error. Use @globals.name to reference a specific global.
Template Variables #
Use {{variable_name}} in prose to embed calculated values. Tags are resolved after all calculations run, so forward references work — put a summary at the top and calculations below:
## Summary
Total cost: {{total_cost}}
Per person: {{per_person}}
headcount = 12
rate = $150000
total_cost = headcount * rate
per_person = total_cost / headcount
The summary renders with $1.8M and $150K substituted in. If a variable is not defined, the {{tag}} is left as-is in the output.
Inline Patterns #
Interpolated values render bold in markdown output and are wrapped in <span class="cm-interpolated"> in HTML. You can combine them with other markdown formatting:
The grand total is {{total_cost}}.
_Headcount: {{team_size}}_
## Budget: {{annual_budget}}
| Metric | Value |
|--------|-------|
| Revenue | {{revenue}} |
| Margin | {{margin}} |
Backticks around {{var}} are automatically stripped — `{{cost}}` renders as $1.8M, not $1.8M. This prevents interpolated values from appearing as inline code in HTML.
Tips #
- Forward references are the killer feature. Write your executive summary first, add calculations below, and the summary stays current when assumptions change.
- Whitespace is tolerated.
{{ total_cost }}works the same as{{total_cost}}. - Undefined variables are safe.
{{unknown}}passes through unchanged — you won’t get errors for tags that don’t match a variable. - Scale and convert_to apply. Interpolated values match what CalcBlock results display, including any frontmatter transforms.
See the Language Reference: Template Interpolation for the complete specification, the Services P&L for a production dashboard with interpolated summary tables, and the Household Budget for inline results in prose.
Scale and Convert #
Use scale and convert_to in the frontmatter to transform results across an entire document. This is useful for recipes, engineering estimates, and any document where you want to change units or multiply quantities globally.
Scaling #
Scaling is explicit — you specify which categories to scale in unit_categories. A bare scale: 2 sets the factor (accessible via @scale) but scales nothing on its own.
---
scale:
factor: 2
unit_categories: [Mass, Volume]
---
flour = 1.5 cups → 3 cups (doubled — Volume)
eggs = 3 eggs → 3 eggs (unchanged — custom unit, not in categories)
oven = 350 fahrenheit → 350 fahrenheit (unchanged — not in categories)
price = $5.00 → $5.00 (unchanged — not in categories)
flour displays as 3 cups because Volume is in the category list. eggs is unchanged because eggs is a custom unit — add Custom to unit_categories to scale it. Temperature and currency are unaffected unless you add Temperature or Currency to unit_categories.
In the editor, scaled values show a × (multiply) suffix in the results pane. Converted values show a • (bullet) suffix. When both scaling and conversion apply, the result shows ו. The context footer shows @scale = N when you’re on a scaled line.
Converting #
Convert all quantities to a target measurement system:
---
convert_to: si
---
distance = 5 miles -> 8.05 km
weight = 10 pounds -> 4.54 kg
temp = 72 fahrenheit -> 22.2222 celsius
distance displays as 8.05 km, weight as 4.54 kg, and temp as 22.2222 celsius. Converted values show a • suffix in the editor results pane. If you use the in keyword explicitly (e.g., distance in feet), that conversion takes priority over convert_to.
Both Together #
When both are present, scale applies first, then convert:
---
scale:
factor: 2
unit_categories: [Mass]
convert_to: si
---
butter = 4 ounces -> 227 g (doubled, then converted)
butter is first doubled to 8 ounces (Mass is in unit_categories), then converted to 227 g. In the editor, this result shows ו — both the scale and convert indicators.
Filtering by Category #
Limit scale or convert to specific unit categories:
---
scale:
factor: 4
unit_categories: [Mass, Volume]
convert_to:
system: si
unit_categories: [Length]
---
Valid categories: Area, Currency, Custom, DataSize, Energy, Length, Mass, Number, Power, Speed, Temperature, Volume. Custom matches units not in the standard library (e.g., bananas, eggs). These are derived from the unit definitions — run cm help frontmatter for the current list. Use unit_categories: [All] to scale every category.
See the Recipe Scaling example for a complete walkthrough, and the Language Reference — Frontmatter for the full specification.
Transform Indicators #
The editor results pane shows symbols next to values that have been transformed:
| Indicator | Meaning | Example |
|---|---|---|
× | Scaled | 4 buns× — factor applied |
• | Converted | 4.54 kg• — unit system changed |
ו | Both | 227 gו — scaled then converted |
| (none) | Untransformed | 10 m — no transforms applied |
Built-in Functions #
accumulate
Calculate total from a rate over time
- Syntax
accumulate(rate, time)- Example
accumulate(100 req/s, 1 hour) → 360000 req
avg
Calculate the average of values
- Syntax
avg(a, b, c, ...)- Aliases
average,mean,average of(input syntax)- Example
avg(10, 20, 30) → 20
capacity
Calculate how many units needed for a given load
- Syntax
capacity(demand, capacity_per_unit, unit) or capacity(demand, capacity_per_unit, unit, buffer)- Aliases
requires- Example
capacity(10000 req/s, 500 req/s, server) → 20 servers
compound
Calculate compound growth over time periods
- Syntax
compound(principal, rate, periods, modifier?)- Aliases
compound...by...over(input syntax),compound...by...monthly...over(input syntax)- Example
compound(1000, 5%, 10 years, monthly) → 1647.01
compress
Estimate compressed data size
- Syntax
compress(size, algorithm)- Aliases
compress...using(input syntax)- Example
compress 1 GB using gzip
convert_rate
Convert a rate to a different time unit
- Syntax
convert_rate(rate, unit)- Example
convert_rate(1000 req/s, minute) → 60000 req/min
depreciate
Calculate declining balance depreciation over time
- Syntax
depreciate(value, rate, periods, salvage?)- Aliases
depreciate...by...over...to(input syntax)- Example
depreciate(10000, 20%, 5) → 3276.80
downtime
Calculate downtime from availability percentage
- Syntax
downtime(availability, period)- Example
downtime(99.9%, month) → 43.2 minutes
grow
Calculate linear growth by adding a fixed amount each period
- Syntax
grow(amount, increment, periods)- Aliases
grow...by...over(input syntax)- Example
grow(100, 20 GB, 5) → 200 GB
number
Extract the numeric value from any type
- Syntax
number(value)- Example
number(10 kg) → 10
read
Time to read data from storage
- Syntax
read(size, storage_type)- Aliases
read...from(input syntax)- Example
read 100 MB from ssd
rtt
Network round-trip time for a scope
- Syntax
rtt(scope)- Aliases
round trip time- Example
rtt(regional) → 10 ms
seek
Access latency for storage type
- Syntax
seek(storage_type)- Example
seek(hdd) → 10 ms
sqrt
Calculate the square root
- Syntax
sqrt(n)- Aliases
square root of(input syntax)- Example
sqrt(16) → 4
sum
Calculate the sum of values
- Syntax
sum(a, b, c, ...)- Aliases
sum of(input syntax),total- Example
sum($100, $200, $300) → $600
throughput
Network bandwidth for a connection type
- Syntax
throughput(network_type)- Example
throughput(gigabit) → 125 MB/s
transfer_time
Time to transfer data over a network
- Syntax
transfer_time(size, scope, network)- Aliases
transfer...across(input syntax)- Example
transfer 1 GB across regional gigabit
See the Function Reference below for detailed examples of every function, including natural language forms.
Function Reference #
Detailed examples for every built-in function, showing both function-call and natural language syntax.
avg / average of
#
avg(10, 20, 30) -> 20
avg(1, 2, 3, 4, 5) -> 3
average of 100, 200, 300 -> 200 (NL form)
avg($100, $200, $300) -> $200.00 (preserves currency)sum / sum of
#
sum($100, $200, $300) -> $600.00
sum(10, 20, 30) -> 60
sum of total_a, total_b, total_c -> (NL form, sums variables)number
#
number(10 kg) -> 10
number($250) -> 250
number(3.5 hours) -> 3.5Strips the unit or currency from any value, returning a plain number. Useful when you need the raw numeric value for a calculation that doesn’t preserve units.
sqrt / square root of
#
sqrt(16) -> 4
sqrt(2) -> 1.4142...
square root of 144 -> 12 (NL form)
sqrt($100) -> $10.00 (preserves currency)accumulate / over
#
accumulate(100 MB/s, 1 hour) -> 360000 MB
accumulate($75/hour, 8 hours) -> $600
100 MB/s over 1 day -> ~8.64 TB (keyword form)
$120000/year over 1 month -> $10000convert_rate
#
convert_rate(1000 req/s, minute) -> 60000 req/min
convert_rate($120000/year, month) -> $10000/monthcapacity / at...per
#
capacity(10 TB, 2 TB, disk) -> 5 disks
capacity(10000 req/s, 500 req/s, server) -> 20 servers
10 TB at 2 TB per disk -> 5 disks (NL form)
10000 req/s at 450 req/s per server with 20% buffer -> 27 serversdowntime
#
downtime(0.999, year) -> 8.76 hours
downtime(0.999, month) -> 43.2 minutes
downtime(0.9999, month) -> 4.32 minutesrtt
#
rtt(local) -> 0.5 ms
rtt(regional) -> 10 ms
rtt(continental) -> 50 ms
rtt(global) -> 150 msthroughput
#
throughput(gigabit) -> 125 MB/s
throughput(ten_gig) -> 1250 MB/s
throughput(wifi) -> 12.5 MB/s
throughput(five_g) -> 50 MB/stransfer_time / transfer...across
#
transfer_time(1 GB, regional, gigabit) -> ~8 seconds
transfer_time(500 MB, continental, gigabit)
transfer 1 GB across regional gigabit -> (NL form)
transfer 100 MB across local ten_gig
data = 500 KB
transfer data across continental ten_gig -> (variable reference)| transfer_time(500 MB, continental, gigabit) | → | 4.05 second |
| transfer 100 MB across local ten_gig | → | 0.0805 second |
| data = 500 KB | → | 500 KB |
read / read...from
#
read(100 MB, ssd) -> ~0.18 seconds
read(1 GB, nvme) -> ~0.29 seconds
read 100 MB from ssd -> (NL form)
read 10 GB from pcie_ssd
data = 5 MB
read data from nvme -> (variable reference)| read 10 GB from pcie_ssd | → | 1.46 second |
| data = 5 MB | → | 5 MB |
seek
#
seek(hdd) -> 10 ms
seek(ssd) -> 0.1 ms
seek(nvme) -> 0.01 ms
db_query_hdd = seek(hdd) + read(5 MB, hdd)| db_query_hdd = seek(hdd) + read(5 MB, hdd) | → | 0.0433 second |
compress / compress...using
#
compress(1 GB, gzip) -> ~333 MB
compress(500 MB, lz4) -> ~250 MB
compress(2 GB, zstd) -> ~571 MB
compress 1 GB using gzip -> (NL form)
compress 500 MB using lz4
data = 1 GB
compress data using zstd -> (variable reference)| compress 500 MB using lz4 | → | 250 MB |
| data = 1 GB | → | 1 GB |
compound / compound...by...over
#
compound($1000, 5%, 10) -> $1628.89
compound(500 customers, 20%, 12) -> 4458.05 customers
compound($1000, 5%, 10 years, monthly) -> $1647.01
compound($1000, 5%, 10 years, quarterly) -> $1643.62
compound $1000 by 5% over 10 years -> $1628.89 (NL form)
compound $1000 by 5% monthly over 10 years
compound $1000 by 5% per month over 12 months| compound $1000 by 5% monthly over 10 years | → | $1,647.01 |
| compound $1000 by 5% per month over 12 months | → | $1,795.86 |
grow / grow...by...over
#
grow(100, 20, 5) -> 200
grow($500, $100, 36) -> $4100.00
grow 100 by 20 over 5 months -> 200 (NL form)depreciate / depreciate...by...over
#
depreciate($50000, 15%, 5) -> $22185.27
depreciate($50000, 15%, 20, $5000) -> $5000.00 (salvage floor)
depreciate $50000 by 15% over 5 years -> $22185.27 (NL form)
depreciate $50000 by 15% over 5 years to $5000| depreciate $50000 by 15% over 5 years to $5000 | → | $22.19K |
Unit Handling in Functions #
Same units are preserved:
avg($100, $200, $300) -> $200.00
sqrt($100) -> $10.00Mixed units are dropped:
avg($100, €200) -> 150 (no units)
average of $50, €100, £150 -> 100 (no units)Natural Language Syntax #
CalcMark supports natural language forms for many functions. These are equivalent to the function-call syntax. Arguments can be literal values (100 MB) or variable references (data).
Function Aliases #
| Natural Language | Equivalent | Example |
|---|---|---|
average of X, Y, Z | avg(X, Y, Z) | average of 10, 20, 30 |
square root of X | sqrt(X) | square root of 144 |
read X from Y | read(X, Y) | read 100 MB from ssd |
compress X using Y | compress(X, Y) | compress 1 GB using gzip |
transfer X across Y Z | transfer_time(X, Y, Z) | transfer 1 GB across regional gigabit |
compound X by Y% over Z | compound(X, Y%, Z) | compound $1000 by 5% over 10 years |
compound X by Y% monthly over Z | compound(X, Y%, Z, monthly) | compound $1000 by 5% monthly over 10 years |
compound X by Y% per P over Z | compound(X, Y%, Z, P) | compound $1000 by 5% per month over 12 months |
compound X by Y% compounded F over Z | compound(X, Y%, Z, compounded F) | compound $1000 by 12% compounded monthly over 10 years |
grow X by Y over Z | grow(X, Y, Z) | grow 100 by 20 over 5 months |
depreciate X by Y% over Z | depreciate(X, Y%, Z) | depreciate $50000 by 15% over 5 years |
depreciate X by Y% over Z to W | depreciate(X, Y%, Z, W) | depreciate $50000 by 15% over 5 years to $5000 |
Capacity Planning Syntax #
The at...per syntax is a natural language form for the capacity() function:
demand at capacity per unit
demand at capacity per unit with N% buffer
Examples:
10 TB at 2 TB per disk -> 5 disks
10000 req/s at 450 req/s per server -> 23 servers
10000 req/s at 450 req/s per server with 20% -> 27 servers
100 apples at 30 per crate -> 4 cratesThe slash syntax also works: 10 TB at 2 TB/disk.
Rate Accumulation with over
#
The over keyword accumulates a rate over a time duration:
rate over duration
This is equivalent to accumulate(rate, duration):
100 MB/s over 1 day -> total data transferred
$75/hour over 8 hours -> daily earnings
1000 req/s over 1 hour -> total requestsRate Conversion with per
#
The per keyword in a rate context creates a rate literal:
1000 requests per second -> 1000 req/s
$50 per hour -> $50/hourNetwork Functions #
Round-Trip Time #
rtt(local) -> 0.5 ms (same datacenter)
rtt(regional) -> 10 ms (same region)
rtt(continental) -> 50 ms (cross-continent)
rtt(global) -> 150 ms (worldwide)Throughput #
throughput(gigabit) -> 125 MB/s
throughput(ten_gig) -> 1.22 GB/s
throughput(hundred_gig) -> 12500 MB/s
throughput(wifi) -> 12.5 MB/s
throughput(four_g) -> 2.5 MB/s
throughput(five_g) -> 50 MB/sTransfer Time #
Calculate data transfer time across a network:
transfer_time(1 GB, regional, gigabit)
transfer 1 GB across regional gigabit (NL form)| transfer_time(1 GB, regional, gigabit) | → | 8.2 second |
Downtime from Availability #
downtime(99.9%, year) -> ~8.76 hours
downtime(99.99%, month) -> ~4.32 minutesStorage Functions #
Read Time #
read(1 GB, ssd) read from SATA SSD (~550 MB/s)
read(1 GB, nvme) read from NVMe SSD (~3.5 GB/s)
read(1 GB, pcie_ssd) read from PCIe Gen4 SSD (~7 GB/s)
read(1 GB, hdd) read from 7200 RPM HDD (~150 MB/s)
read 100 MB from ssd (NL form)Seek Latency #
seek(ssd) -> 0.1 ms
seek(nvme) -> 0.01 ms
seek(pcie_ssd) -> 0.01 ms
seek(hdd) -> 10 msCompression #
compress(1 GB, gzip) -> 333 MB (3:1 ratio)
compress(1 GB, lz4) -> 512 MB (2:1 ratio)
compress(1 GB, zstd) -> 293 MB (3.5:1 ratio)
compress(1 GB, bzip2) -> 250 MB (4:1 ratio)
compress(1 GB, snappy) -> 400 MB (2.5:1 ratio)
compress(1 GB, none) -> 1 GB (1:1, no compression)
compress 1 GB using gzip (NL form)Growth Functions #
Compound Growth #
The 4th argument controls which formula compound() uses. This matters – monthly and month give different answers:
Without a frequency modifier – simple compound growth, once per year:
A = P × (1 + r)^t
compound($1000, 5%, 10) -> $1628.89
compound $1000 by 5% over 10 years -> $1628.895% annual rate applied once per year for 10 years. A plain number like 10 means 10 years.
With a frequency adverb (monthly, quarterly, weekly, daily, yearly) – financial compounding, multiple times per year:
A = P × (1 + r/n)^(n × t)
where r is the annual rate, n is compounding frequency per year, and t is years.
compound($1000, 5%, 10, monthly) -> $1647.01
compound($1000, 5%, 10, quarterly) -> $1643.62
compound($1000, 5%, 24 months, monthly) -> $1103.43
compound $1000 by 5% monthly over 10 years -> $1647.01The 5% annual rate is split into 12 monthly applications (5%/12 per month), compounded 120 times over 10 years. More frequent compounding yields higher returns: monthly ($1,647) > quarterly ($1,643) > yearly ($1,629).
Arguments:
| # | Name | Description |
|---|---|---|
| 1 | principal | Starting amount (number, currency, or quantity) |
| 2 | rate | Annual growth rate as percentage |
| 3 | duration | Time in years. A plain number like 10 means 10 years. Durations like 24 months are converted to years |
| 4 | modifier | Optional: monthly, quarterly, daily, weekly, yearly. Compounds multiple times per year instead of once |
month vs monthly: The noun month is a period label – compound($1000, 5%, 12, month) means “5% per month, applied 12 times” (the rate is per-month, not annual). The adverb monthly means “5% annual rate, compounded 12 times per year” – a different formula with a different result.Linear Growth #
Calculate linear (additive) growth over time:
grow($500, $100, 36) -> $4100.00
grow(100, 20, 5) -> 200
grow 100 by 20 over 5 months (NL form)Arguments:
| # | Name | Description |
|---|---|---|
| 1 | initial | Starting amount |
| 2 | increment | Amount added each period |
| 3 | periods | Number of periods (number or duration) |
Depreciation #
Calculate declining-balance depreciation with optional salvage floor:
depreciate($50000, 15%, 5) -> $22185.27
depreciate($50000, 15%, 20, $5000) -> $5000.00 (salvage floor)
depreciate $50000 by 15% over 5 years (NL form)
depreciate $50000 by 15% over 5 years to $5000| depreciate $50000 by 15% over 5 years to $5000 | → | $22.19K |
Arguments:
| # | Name | Description |
|---|---|---|
| 1 | value | Starting value |
| 2 | rate | Depreciation rate as percentage |
| 3 | periods | Number of periods (number or duration) |
| 4 | salvage | Optional: minimum floor value |
Rates and the over keyword
#
Rate Literals #
Create rates using the slash syntax:
net_bandwidth = 100 MB/s
salary = $120000/year
load = 1000 req/s| net_bandwidth = 100 MB/s | → | 100 MB/s |
| salary = $120000/year | → | 120K $/year |
| load = 1000 req/s | → | 1K req/s |
Rate Accumulation with over
#
Use over to calculate the total from a rate over time:
link_speed = 100 MB/s
daily_transfer = link_speed over 1 day
hourly_rate = $75/hour
daily_earnings = hourly_rate over 8 hours| link_speed = 100 MB/s | → | 100 MB/s |
| daily_transfer = link_speed over 1 day | → | 8.24 TB |
| hourly_rate = $75/hour | → | 75 $/h |
| daily_earnings = hourly_rate over 8 hours | → | $600.00 |
Rate Conversion #
Convert rates to different time units using convert_rate():
convert_rate(1000 req/s, minute) -> 60000 req/min
convert_rate($120000/year, month) -> $10000/monthRate Arithmetic Widening #
When you multiply or divide by a rate (rate on the right), CalcMark drops the time denominator and uses the rate’s amount. This is called widening — the rate widens into a plain quantity.
When the rate is on the left side, it stays a rate. This lets you scale rates naturally with rate * number.
posts_rate = 2 posts/week
scaled = posts_rate * 3 -> 6 posts/week (rate * number → rate)
total = 3 * posts_rate -> 6 posts (number * rate → quantity)
The rule is simple: left operand wins. If you start with a rate, you get a rate. If you start with a number or quantity and multiply by a rate, you get a number or quantity.
See the Language Reference: Rate Arithmetic Widening for the full type dispatch table.
Date Arithmetic #
Date Literals #
project_start = Jan 15 2025
christmas = Dec 25 2025
now = today| project_start = Jan 15 2025 | → | Wednesday, January 15, 2025 |
| christmas = Dec 25 2025 | → | Thursday, December 25, 2025 |
| now = today | → | Saturday, March 14, 2026 |
CalcMark recognizes today, tomorrow, and yesterday as date keywords.
Duration Arithmetic #
project_start = Jan 15 2025
duration = 12 weeks
project_end = project_start + duration
deadline = Jun 1 2025
launch = deadline - 2 weeks| project_start = Jan 15 2025 | → | Wednesday, January 15, 2025 |
| duration = 12 weeks | → | 12 week |
| project_end = project_start + duration | → | Wednesday, April 9, 2025 |
| deadline = Jun 1 2025 | → | Sunday, June 1, 2025 |
| launch = deadline - 2 weeks | → | Sunday, May 18, 2025 |
The from Keyword
#
7 days from Jan 1 2025 -> Wednesday, January 8, 2025
2 weeks from today -> (today + 14 days)Napkin Math #
The as napkin modifier rounds results to 2 significant figures and normalizes units. It adds a ~ prefix to signal the result is an approximation.
Syntax: expression as napkin
Works with: Number, Quantity, Currency, Duration, Rate
432000 MB as napkin -> ~420 GB
100 MB/s over 30 days as napkin -> ~248 TB
$1234567 as napkin -> ~$1.2MThis is useful for quick back-of-the-envelope calculations where exact precision is not needed.
Precise Display #
The as precise modifier is the opposite of as napkin. It shows full float precision, skipping all display rounding. This is useful when you need exact values from unit conversions.
Syntax: expression as precise
10 meters as feet -> 32.8 feet
10 meters as feet as precise -> 32.808399 feetExplicit unit conversions are rounded by default for readability. Use as precise when you need the exact value.
Capacity Planning #
Use the at...per syntax to calculate how many units you need:
disks = 10 TB at 2 TB per disk
servers = 10000 req/s at 450 req/s per server
servers_buffered = 10000 req/s at 450 req/s per server with 20% buffer| disks = 10 TB at 2 TB per disk | → | 5 disk |
| servers = 10000 req/s at 450 req/s per server | → | 23 server |
| servers_buffered = 10000 req/s at 450 req/s per server with 20% buffer | → | 27 server |
Multiplier Suffixes #
Use K, M, B for large numbers:
users = 10M
revenue = $5B
requests = 100K| users = 10M | → | 10M |
| revenue = $5B | → | $5B |
| requests = 100K | → | 100K |
Percentages #
Percentages work naturally in calculations:
price = $100
discount = 20%
sale_price = price * (1 - discount)
tax_rate = 8.25%
tax = price * tax_rate| price = $100 | → | $100.00 |
| discount = 20% | → | 20% |
| sale_price = price * (1 - discount) | → | $80.00 |
| tax_rate = 8.25% | → | 8.25% |
| tax = price * tax_rate | → | $8.25 |
Worked Examples #
Basic Calculations #
# Simple Math
5 + 3
10 - 2
4 * 5
20 / 4
2 ^ 3
10 % 3| 5 + 3 | → | 8 |
| 10 - 2 | → | 8 |
| 4 * 5 | → | 20 |
| 20 / 4 | → | 5 |
| 2 ^ 3 | → | 8 |
| 10 % 3 | → | 1 |
Variables #
# Budget
salary = $5000
bonus = $500
expenses = $3000
net = salary + bonus - expenses| salary = $5000 | → | $5,000.00 |
| bonus = $500 | → | $500.00 |
| expenses = $3000 | → | $3,000.00 |
| net = salary + bonus - expenses | → | $2,500.00 |
Comparisons #
# Checks
salary = $50000
threshold = $60000
is_high_earner = salary > threshold
needs_raise = salary < $40000
meets_target = salary >= $50000| salary = $50000 | → | $50K |
| threshold = $60000 | → | $60K |
| is_high_earner = salary > threshold | → | false |
| needs_raise = salary < $40000 | → | false |
| meets_target = salary >= $50000 | → | true |
Complex Expressions #
# Mortgage
principal = $200000
rate = 0.04
years = 30
months = years * 12
monthly_rate = rate / 12
payment = principal * (monthly_rate * (1 + monthly_rate) ^ months) / ((1 + monthly_rate) ^ months - 1)| principal = $200000 | → | $200K |
| rate = 0.04 | → | 0.04 |
| years = 30 | → | 30 |
| months = years * 12 | → | 360 |
| monthly_rate = rate / 12 | → | 0.003333 |
| payment = principal * (monthly_rate * (1 + monthly_rate) ^ months) / ((1 + monthly_rate) ^ months - 1) | → | $954.83 |
System Sizing #
# Server Capacity Planning
peak_load = 50000 req/s
server_capacity = 2000 req/s
servers = peak_load at server_capacity per server with 25% buffer
# Storage
daily_data = 100 MB/s over 1 day
monthly_storage = daily_data * 30
disks = monthly_storage at 2 TB per disk| peak_load = 50000 req/s | → | 50K req/s |
| server_capacity = 2000 req/s | → | 2K req/s |
| servers = peak_load at server_capacity per server with 25% buffer | → | 32 server |
| daily_data = 100 MB/s over 1 day | → | 8.24 TB |
| monthly_storage = daily_data * 30 | → | 247 TB |
| disks = monthly_storage at 2 TB per disk | → | 124 disk |
Mixed Markdown #
# My Monthly Budget
I earn a salary and get bonuses.
## Income
monthly_salary = $5000
annual_bonus = $3000
monthly_bonus = annual_bonus / 12
Total monthly income:
total_income = monthly_salary + monthly_bonus
## Expenses
- Rent: $1500
- Food: $600
- Utilities: $200
rent = $1500
food = $600
utilities = $200
total_expenses = rent + food + utilities
## Summary
Monthly surplus:
surplus = total_income - total_expenses
Can I save 20%?
savings_goal = total_income * 0.20
can_save = surplus >= savings_goal| monthly_salary = $5000 | → | $5,000.00 |
| annual_bonus = $3000 | → | $3,000.00 |
| monthly_bonus = annual_bonus / 12 | → | $250.00 |
| total_income = monthly_salary + monthly_bonus | → | $5,250.00 |
| rent = $1500 | → | $1,500.00 |
| food = $600 | → | $600.00 |
| utilities = $200 | → | $200.00 |
| total_expenses = rent + food + utilities | → | $2,300.00 |
| surplus = total_income - total_expenses | → | $2,950.00 |
| savings_goal = total_income * 0.20 | → | $1,050.00 |
| can_save = surplus >= savings_goal | → | true |
Tips #
Organize with Markdown #
Use headers and prose to structure your thinking:
# Q1 Budget
## Revenue Assumptions
monthly_revenue = $50000
q1_months = 3
total_revenue = monthly_revenue * q1_months
## Cost Breakdown
fixed_costs = $20000
variable_pct = 30%
variable_costs = total_revenue * variable_pct| monthly_revenue = $50000 | → | $50K |
| q1_months = 3 | → | 3 |
| total_revenue = monthly_revenue * q1_months | → | $150K |
| fixed_costs = $20000 | → | $20K |
| variable_pct = 30% | → | 30% |
| variable_costs = total_revenue * variable_pct | → | $45K |
Use the Preview Pane #
Press Ctrl+P in the editor to toggle the preview pane, which shows evaluated results alongside your source.
Get Help on Functions #
Run cm help functions to see all available functions with descriptions and usage patterns. Run cm help constants for unit constants, or cm help frontmatter for frontmatter directives.
Troubleshooting #
“Undefined variable” #
Variables must be defined before use. Check that:
- The variable is spelled correctly
- It’s defined on an earlier line
- No typos in the name
“Incompatible units” #
You can’t add meters to kilograms. Check that operations make physical sense.
“Parse error” #
The line isn’t valid CalcMark syntax. Common issues:
- Missing operator between values
- Unclosed parentheses
- Invalid characters