# Introduction

“You see, Vergon 6 was once filled with the super-dense substance known as dark matter, each pound of which weighs over 10,000 pounds.” — Futurama, S1E4

Numbat is a statically typed programming language for scientific computations with first class support for physical dimensions and units.

You can use it for simple mathematical computations:

>>> 1920/16*9

= 1080

>>> 2^32

= 4294967296

>>> sqrt(1.4^2 + 1.5^2) * cos(pi/3)^2

= 0.512957


The real strength of Numbat, however, is to perform calculations with physical units:

>>> 8 km / (1 h + 25 min)

8 kilometer / (1 hour + 25 minute)

= 5.64706 km/h    [Velocity]

>>> 140 € -> GBP

140 euro ➞ british_pound

= 120.768 £    [Money]

>>> atan2(30 cm, 1 m) -> deg

atan2(30 centimeter, 1 meter) ➞ degree

= 16.6992°

>>> let ω = 2π c / 660 nm

let ω: Frequency = 2 π × c / 660 nanometer

>>> ℏ ω -> eV

ℏ × ω ➞ electronvolt

= 1.87855 eV    [Energy]


# Tutorial

In this tutorial, you will use Numbat to calculate how many bananas you would need to power a house. This is based on an article in the great what if? series by the author of the xkcd comics.

Bananas contain potassium. In its natural form, potassium contains a tiny fraction (0.0117%) of the isotope 40K, which is radioactive. The idea is to use the radioactive decay energy as a power source. Open an interactive Numbat session by typing numbat in your favorite terminal emulator. We start by entering a few facts about potassium-40:

let halflife = 1.25 billion years
let occurrence = 0.0117%
let molar_mass = 40 g / mol


New constants are introduced with the let keyword. We define these physical quantities with their respective physical units (years, percent, g / mol) in order to profit from Numbat’s unit-safety and unit-conversion features later on.

Our first goal is to compute the radioactivity of natural potassium. Instead of dealing with the half-life, we want to know the decay rate. When entering the following computation, you can try Numbat’s auto-completion functionality. Instead of typing out halflife, just type half and press Tab.

let decay_rate = ln(2) / halflife


As you can see, we can use typical mathematical functions such as the natural logarithm ln. Next, we are interested how much radioactivity comes from a certain mass of potassium:

let radioactivity =
N_A * occurrence * decay_rate / molar_mass -> Bq / g


The -> Bq / g part at the end converts the expression to Becquerel per gram. If you type in radioactivity, you should see a result of roughly 31 Bq / g, i.e. 31 radioactive decays per second, per gram of potassium.

The unit conversion also serves another purpose. If anything would be wrong with our calculation at the units-level, Numbat would detect that and show an error. Unit safety is a powerful concept not just because you can eliminate an entire category of errors, but also because it makes your computations more readable.

We are interested in the radioactivity of bananas, so we first introduce a new (base) unit:

unit banana


This lets us write readable code like

let potassium_per_banana = 451 mg / banana



and should give you a result of roughly 14 Bq / banana. Adding unit conversions at the end of unit definitions is one way to enforce unit safety. An even more powerful way to do this is to add type annotations: For example, to define the decay energy for a single potassium-40 atom, you can optionally add a : Energy annotation that will be enforced by Numbat:

let energy_per_decay: Energy = 11 percent × 1.5 MeV + 89 percent × 1.3 MeV


This also works with custom units since Numbat adds new physical dimensions (types) implicitly:

let power_per_banana: Power / Banana = radioactivity_banana * energy_per_decay


You’ll also notice that types can be combined via mathematical operators such as / in this example.

How many bananas we need to power a household is going to depend on the average power consumption of that household. So we are defining a simple function

fn household_power(annual_consumption: Energy) -> Power = annual_consumption / year


This allows us to finally answer the original question (for a typical US household in 2021)

household_power(10000 kWh) / power_per_banana


This should give you a result of roughly 4×1014 bananas1.

The images in this tutorial are from https://what-if.xkcd.com/158/. They are licensed under the Creative Commons Attribution-NonCommercial 2.5 License. Details and usage notes can be found at https://xkcd.com/license.html.

1

Interestingly, the what if article comes up with a result of 300 quadrillion bananas, or 3 × 1017. This is a factor of 1000 higher. This seems like a mistake in the original source. All of our other intermediate results are consistent with what has been computed in the original article.

# Examples

This chapter shows some exemplary Numbat programs from various disciplines and sciences.

# Acidity

Run this example

# Compute the pH (acidity) of a solution based
# on the activity of hydrogen ions
#
# https://en.wikipedia.org/wiki/PH

fn pH_acidity(activity_hplus: Molarity) -> Scalar =
- log10(activity_hplus / (mol / L))

print(pH_acidity(5e-6 mol / L))


# Barometric formula

Run this example

# This script calculates the air pressure at a specified
# height above sea level using the barometric formula.

let p0: Pressure = 1 atm
let t0: Temperature = 288.15 K

dimension TemperatureGradient = Temperature / Length
let lapse_rate: TemperatureGradient = 0.65 K / 100 m

fn air_pressure(height: Length) -> Pressure =
p0 · (1 - lapse_rate · height / t0)^5.255

print("Air pressure 1500 m above sea level: {air_pressure(1500 m) -> hPa}")


# Body mass index

Run this example

# This script calculates the Body Mass Index (BMI) based on
# the provided mass and height values.

unit BMI: Mass / Length^2 = kg / m^2

fn body_mass_index(mass: Mass, height: Length) =
mass / height² -> BMI

print(body_mass_index(70 kg, 1.75 m))


# Factorial

Run this example

# Naive factorial implementation to showcase recursive
# functions and conditionals.

fn factorial(n: Scalar) -> Scalar =
if n < 1
then 1
else n × factorial(n - 1)

# Compare result with the builtin factorial operator
assert_eq(factorial(10), 10!)


# Flow rate in a pipe

Run this example

# This script calculates and prints the flow rate in a pipe
# using the Hagen-Poiseuille equation. It assumes the dynamic
# viscosity of water and allows for inputs of pipe radius,
# pipe length, and pressure difference.

let μ_water: DynamicViscosity = 1 mPa·s

fn flow_rate(radius: Length, length: Length, Δp: Pressure) -> FlowRate =
π × radius^4 × Δp / (8 μ_water × length)

let pipe_length = 10 m
let Δp = 0.1 bar

let Q = flow_rate(pipe_radius, pipe_length, Δp)
print("Flow rate: {Q -> L/s}")


# Medication dosage

Run this example

# This script calculates the total daily dose and per intake
# dose of a medication based on a person's body weight.

@aliases(takings)
unit taking

let body_weight = 75 kg
let dosage = (60 mg / kg) / day
let frequency = 3 takings / day

let total_daily_dose = dosage * body_weight -> mg / day
print("Total daily dose: {total_daily_dose}")

let single_dose = total_daily_dose / frequency
print("Single dose:      {single_dose}")


# Molarity

Run this example

# This script calculates and prints the molarity of a salt
# water solution, given a fixed mass of salt (NaCl) and a
# volume of water.

let molar_mass_NaCl = 58.44 g / mol

fn molarity(mass: Mass, volume: Volume) -> Molarity =
(mass / molar_mass_NaCl) / volume

let salt_mass = 9 g
let water_volume = 1 L

print(molarity(salt_mass, water_volume) -> mmol / l)


# Musical note frequency

Run this example

# Musical note frequencies in the 12 equal temperament system

let frequency_A4: Frequency = 440 Hz  # the A above middle C, A4

fn note_frequency(n: Scalar) -> Frequency = frequency_A4 * 2^(n / 12)

print("A5: {note_frequency(12)}")  # one octave higher up, 880 Hz
print("E4: {note_frequency(7)}")
print("C4: {note_frequency(-3)}")


# Paper sizes

Run this example

# Compute ISO 216 paper sizes for the A series
#
# https://en.wikipedia.org/wiki/ISO_216

struct PaperSize {
width: Length,
height: Length,
}

fn paper_size_A(n: Scalar) -> PaperSize =
if n == 0
then
PaperSize {
width: 841 mm,
height: 1189 mm
}
else
PaperSize {
width: floor(paper_size_A(n - 1).height / 2),
height: paper_size_A(n - 1).width,
}

fn paper_area(size: PaperSize) -> Area =
size.width * size.height

fn size_as_string(size: PaperSize) = "{size.width:>4} × {size.height:>5}   {paper_area(size) -> cm²:>6.1f}"
fn row(n) = "A{n:<3}   {size_as_string(paper_size_A(n))}"

print("Name    Width     Height        Area  ")
print("----   -------   --------   ----------")
print(join(map(row, range(0, 10)), "\n"))


# Population growth

Run this example

# Exponential model for population growth

let initial_population = 50_000 people
let growth_rate = 2% per year

fn predict_population(t: Time) =
initial_population × e^(growth_rate·t) // round

print("Population in  20 years: {predict_population(20 years)}")
print("Population in 100 years: {predict_population(1 century)}")


# Recipe

Run this example

# Scale ingredient quantities based on desired servings.

@aliases(servings)
unit serving

let original_recipe_servings = 2 servings
let desired_servings = 3 servings

fn scale(quantity) =
quantity × desired_servings / original_recipe_servings

print("Milk:          {scale(500 ml)}")
print("Flour:         {scale(250 g)}")
print("Sugar:         {scale(2 cups)}")
print("Baking powder: {scale(4 tablespoons)}")


# Voyager

Run this example

# How many photons are received per bit transmitted from Voyager 1?
#
# This calculation is adapted from a Physics Stack Exchange answer [1].
#
# [1] https://physics.stackexchange.com/a/816710

let datarate = 160 bps
let f = 8.3 GHz
let P_transmit = 23 W

let ω = 2π f
let λ = c / f

@aliases(photon)
unit photons

let energy_per_photon = ℏ ω / photon

let photon_rate = P_transmit / energy_per_photon -> photons/s

print("Voyager sends data at a rate of {datarate} with {P_transmit}.")
print("At a frequency of {f}, this amounts to {photon_rate:.0e}.")

# Voyager dish antenna:
let d_voyager = 3.7 m

# Voyagers distance to Earth:
let R = 23.5 billion kilometers  # as of 2024

let irradiance = P_transmit / (4π R²)

let photons_per_bit = photon_rate_receiver / datarate -> photons/bit

print()
print("Which means {photons_per_bit:.0}.")


# XKCD 687

Run this example

# Dimensional analysis
#
# https://xkcd.com/687/

let core_pressure = 3.5 million atmospheres
let prius_milage = 50 miles per gallon
let min_width_channel = 21 miles

# Make sure that the result is dimensionless:
let r: Scalar =
planck_energy / core_pressure × prius_milage / min_width_channel

print("{r} ≈ π ?")


# XKCD 2585

Run this example

# Rounding
#
# https://xkcd.com/2585/

17 mph

ans -> meters/sec    // round
ans -> knots         // round
ans -> fathoms/sec   // round
ans -> furlongs/min  // round
ans -> fathoms/sec   // round
ans -> kph           // round
ans -> knots         // round
ans -> kph           // round
ans -> furlongs/hour // round
ans -> mph           // round
ans -> m/s           // round
ans -> furlongs/min  // round
ans -> yards/sec     // round
ans -> fathoms/sec   // round
ans -> m/s           // round
ans -> mph           // round
ans -> furlongs/min  // round
ans -> knots         // round
ans -> yards/sec     // round
ans -> fathoms/sec   // round
ans -> knots         // round
ans -> furlongs/min  // round
ans -> mph           // round

print("I can ride my bike at {ans}.")
print("If you round.")


# XKCD 2812

Run this example

# Solar panel placement
#
# Solar energy tip: To maximize sun exposure, always
# orient your panels downward and install them on the
# surface of the sun.
#
# https://xkcd.com/2812/
#
# [1] https://en.wikipedia.org/wiki/Solar_luminosity
# [2] https://en.wikipedia.org/wiki/Sun

unit $: Money let net_metering_rate =$ 0.20 / kWh
let panel_area = 1 m²
let panel_efficiency = 20 %

fn savings(i: Irradiance) -> Money / Time =
net_metering_rate × i × panel_area × panel_efficiency -> $/year print("Option A: On the roof, south facing") let savings_a = savings(4 kWh/m²/day) print(savings_a // round) print() print("Option B: On the sun, downward facing") dimension Luminosity = Power let sun_luminosity: Luminosity = 3.828e26 W # [1] let sun_area: Area = 6.09e12 km^2 # [2] let savings_b = savings(sun_luminosity / sun_area) print(savings_b // round)  # IDE / editor integration There is syntax highlighting support for the following IDEs / text editors: # Basics This chapter introduces language features that are required to perform basic computations in Numbat or to write small programs. # Number notation Numbers in Numbat can be written in the following forms: • Integer notation • 12345 • 12_345 — with decimal separators • Floating point notation • 0.234 • .234 — without the leading zero • Scientific notation • 1.234e15 • 1.234e+15 • 1e-9 • 1.0e-9 • Non-decimal bases notation • 0x2A — Hexadecimal • 0o52 — Octal • 0b101010 — Binary • Non-finite numbers • NaN — Not a number • inf — Infinity ## Convert numbers to other bases You can use the bin, oct, dec and hex functions to convert numbers to binary, octal, decimal and hexadecimal bases, respectively. You can call those using hex(2^16 - 1), or 2^16 - 1 // hex, but they are also available as targets of the conversion operator ->/to, so you can write expressions like: Examples: 0xffee to bin 42 to oct 2^16 - 1 to hex  You can also use base(b) to convert a number to base b: 0xffee to base(2)  # Unit notation Most units can be entered in the same way that they would appear in textbook calculations. They usually have a long form (meter, degrees, byte, …), a plural form (meters, degrees, bytes), and a short alias (m, °, B). For a full list of supported units, see this page. All SI-accepted units support metric prefixes (mm, cm, km, … or millimeter, centimeter, kilometer, …) and — where sensible — units allow for binary prefixes (MiB, GiB, … or mebibyte, gibibyte, …). Note that the short-form prefixes can only be used with the short version of the unit, and vice versa (that is: kmeter and kilom are not allowed, only km and kilometer). Units can be combined using mathematical operations such as multiplication, division and exponentiation: kg * m/s^2, km/h, m², meter per second. The following snippet shows various styles of entering units: 2 min + 1 s 150 cm sin(30°) 50 mph 6 MiB 2 minutes + 1 second 150 centimeters sin(30 degrees) 50 miles per hour 6 mebibyte  Note that Numbat also allows you to define new units. # Operations and precedence Numbat operators and other language constructs, ordered by precedence form high to low: Operation / operatorSyntax square, cube, …x², x³, x⁻¹, … factorialx! exponentiationx^y, x**y multiplication (implicit)x y (whitespace) unary negation-x divisionx per y divisionx / y, x ÷ y multiplication (explicit)x * y, x · y, x × y subtractionx - y additionx + y comparisonsx < y, x <= y, x ≤ y, … x == y, x != y logical negation!x logical ‘and’x && y logical ‘or’x || y unit conversionx -> y, x → y, x ➞ y, x to y conditionalsif x then y else z reverse function callx // f Note that implicit multiplication has a higher precedence than division, i.e. 50 cm / 2 m will be parsed as 50 cm / (2 m). Also, note that per-division has a higher precedence than /-division. This means 1 / meter per second will be parsed as 1 / (meter per second). If in doubt, you can always look at the pretty-printing output (second line in the snippet below) to make sure that your input was parsed correctly: >>> 1 / meter per second 1 / (meter / second) = 1 s/m  # Constants New constants can be introduced with the let keyword: let pipe_radius = 1 cm let pipe_length = 10 m let Δp = 0.1 bar  Definitions may contain a type annotation after the identifier (let Δp: Pressure = 0.1 bar). This annotation will be verified by the type checker. For more complex definitions it can be desirable to add type annotations, as it often improves readability and allows you to catch potential errors early: let μ_water: DynamicViscosity = 1 mPa·s let Q: FlowRate = π × pipe_radius^4 × Δp / (8 μ_water × pipe_length)  # Unit conversions The conversion operator -> attempts to convert the physical quantity on its left hand side to the unit of the expression on its right hand side. This means that you can write an arbitrary expression on the right hand side — but only the unit part will be extracted. For example: # simple unit conversion: > 120 km/h -> mph = 74.5645 mi/h # expression on the right hand side: > 120 m^3 -> km * m^2 = 0.12 m²·km # convert x1 to the same unit as x2: > let x1 = 50 km / h > let x2 = 3 m/s -> x1 x2 = 10.8 km/h  # Conversion functions The conversion operator -> (or to) can not just be used for unit conversions, but also for other types of conversions. The way this is set up in Numbat is that you can call x -> f for any function f that takes a single argument of the same type as x. The following functions are available for this purpose: # Convert a date and time to a Unix timestamp now() -> unixtime # Convert a date and time to a different timezone now() -> tz("Asia/Kathmandu") # Convert a duration to days, hours, minutes, seconds 10 million seconds -> human # Convert a number to its binary representation 42 -> bin # Convert a number to its octal representation 42 -> oct # Convert a number to its hexadecimal representation 2^31-1 -> hex # Convert a number to a custom base 42 -> base(16) # Convert an ASCII code point number to a character 78 -> chr # Convert a string to upper/lower case "numbat is awesome" -> uppercase "vier bis elf weiße Querbänder" -> lowercase  Note that the tz(…) and base(…) calls above return functions, i.e. the right hand side of the conversion operator is still a function. # Function definitions Numbat comes with a large number of predefined functions, but it is also possible to add new functions. A function definition is introduced with the fn keyword: fn max_distance(v: Velocity, θ: Angle) -> Length = v² · sin(2 θ) / g0  This exemplary function computes the maximum distance of a projectile under the influence of Earths gravity. It takes two parameters (The initial velocity v and the launch angle θ), which are both annotated with their corresponding physical dimension (their type). The function returns a distance, and so the return type is specified as Length. ## Type inference The return type annotation may be omitted, but it is often desirable to add it for better readability of the code and in order to catch potential errors. The parameter types can also (sometimes) be omitted, in which case Numbat tries to infer their type. However, this often leads to overly generic function signatures. For example, consider the following function to compute the kinetic energy of a massive object in motion: fn kinetic_energy(mass, speed) = 1/2 * mass * speed^2  Without any type annotations, this function has an overly generic type where mass and speed can have arbitrary dimensions (and the return type is type(mass) * type(speed)^2). So for this case, it is probably better to add parameter and return types. ## Generic functions Sometimes however, it is useful to write generic functions. For example, consider max(a, b) — a function that returns the larger of the two arguments. We might want to use that function with dimensionful arguments such as max(1 m, 1 yd). To define such a generic function, you can introduce type parameters in angle brackets: fn max<T>(a: T, b: T) -> T = if a > b then a else b  This function signature tells us that max takes two arguments of arbitrary type T (but they need to match!), and returns a quantity of the same type T. Note that you can perform the usual operations with type parameters, such as multiplying/dividing them with other types, or raising to rational powers. For example, consider this cube-root function fn cube_root<T>(x: T^3) -> T = x^(1/3)  that can be called with a scalar (cube_root(8) == 2) or a dimensionful argument (cube_root(1 liter) == 10 cm). Note: cube_root can also be defined as fn cube_root<T>(x: T) -> T^(1/3), which is equivalent to the definition above. ## Recursive functions It is also possible to define recursive functions. In order to do so, you currently need to specify the return type — as the type signature can not (yet) be inferred otherwise. For example, a naive recursive implementation to compute Fibonacci numbers in Numbat looks like this: fn fib(n: Scalar) -> Scalar = if n ≤ 2 then 1 else fib(n - 2) + fib(n - 1)  # Conditionals Numbat has if-then-else conditional expressions with the following syntax if <cond> then <expr1> else <expr2>  where <cond> is a condition that evaluates to a Boolean value, like 3 ft < 3 m. The types of <expr1> and <expr2> need to match. For example, you can defined a simple step function using fn step(x: Scalar) -> Scalar = if x < 0 then 0 else 1  # Date and time Numbat supports date and time handling based on the proleptic Gregorian calendar, which is the (usual) Gregorian calendar extended to dates before its introduction in 1582. Julian calendar dates are currently not supported. A few examples of useful operations that can be performed on dates and times: # Which date is 40 days from now? now() + 40 days # Which date was 1 million seconds ago? now() - 1 million seconds # How many days are left until September 1st? date("2024-11-01") - today() -> days # What time is it in Nepal right now? now() -> tz("Asia/Kathmandu") # use tab completion to find time zone names # What is the local time when it is 2024-11-01 12:30:00 in Australia? datetime("2024-11-01 12:30:00 Australia/Sydney") -> local # What is the current UNIX timestamp? now() -> unixtime # What is the date corresponding to the UNIX timestamp 1707568901? from_unixtime(1707568901) # How long are one million seconds in days, hours, minutes, seconds 1 million seconds -> human  ## Date and time arithmetic The following operations are supported for DateTime objects: LeftOperatorRightResult DateTime-DateTimeDuration between the two dates as a Time. In seconds, by default. Use normal conversion for other time units. DateTime+TimeNew DateTime by adding the duration to the date DateTime-TimeNew DateTime by subtracting the duration from the date DateTime->tz("…")Converts the datetime to the specified time zone. Note that you can use tab-completion for time zone names. Warning: You can add years or months to a given date (now() + 3 months), but note that the result might not be what you expect. The unit year is defined as the average length of a year (a tropical year, to be precise), and month is defined as the average length of a month (1/12 of a year). So this does not take into account the actual length of the months or the leap years. However, note that adding or subtracting “one year” or “one month” is not a well-defined operation anyway. For example, what should “one month after March 31st” be? April 30th or May 1st? If your answer is April 30th, then what is “one month after March 30th”? If your answer is May 1st, then what is “one month after April 1st”? ## Date, time, and duration functions The following functions are available for date and time handling: • now() -> DateTime: Returns the current date and time. • today() -> DateTime: Returns the current date at midnight (in the local time). • datetime(input: String) -> DateTime: Parses a string (date and time) into a DateTime object. • date(input: String) -> DateTime: Parses a string (only date) into a DateTime object. • time(input: String) -> DateTime: Parses a string (only time) into a DateTime object. • format_datetime(format: String, dt: DateTime) -> String: Formats a DateTime object as a string. See this page for possible format specifiers. • tz(tz: String) -> Fn[(DateTime) -> DateTime]: Returns a timezone conversion function, typically used with the conversion operator (datetime -> tz("Europe/Berlin")) • local(dt: DateTime) -> DateTime: Timezone conversion function targeting the users local timezone (datetime -> local) • get_local_timezone() -> String: Returns the users local timezone • unixtime(dt: DateTime) -> Scalar: Converts a DateTime to a UNIX timestamp. • from_unixtime(ut: Scalar) -> DateTime: Converts a UNIX timestamp to a DateTime object. • human(duration: Time) -> String: Converts a Time to a human-readable string in days, hours, minutes and seconds ## Date time formats The following formats are supported by datetime. UTC offsets are mandatory for the RFC 3339 and RFC 2822 formats. The other formats can optionally include a time zone name or UTC offset. If no time zone is specified, the local time zone is used. FormatExamples RFC 33392024-02-10T12:30:00Z 2024-02-10T06:30:00-06:00 RFC 2822Sat, 10 Feb 2024 12:30:00 Z Sat, 10 Feb 2024 06:30:00 -0600 %Y-%m-%d %H:%M:%S%.f2024-02-10 12:30:00 2024-02-10 06:30:00 -0600 2024-02-10 07:30:00 US/Eastern 2024-02-10 12:30:00.123456 %Y/%m/%d %H:%M:%S%.fsame, but with / separator %Y-%m-%d %H:%M2024-02-10 12:30 2024-02-10 06:30 -0600 2024-02-10 07:30 US/Eastern %Y/%m/%d %H:%Msame, but with / separator %Y-%m-%d %I:%M:%S%.f %p2024-02-10 12:30:00 PM 2024-02-10 06:30:00 AM -0600 2024-02-10 07:30:00 AM US/Eastern 2024-02-10 12:30:00.123456 PM %Y/%m/%d %I:%M:%S%.f %psame, but with / separator %Y-%m-%d %I:%M %p2024-02-10 12:30 PM 2024-02-10 06:30 AM -0600 2024-02-10 07:30 AM US/Eastern %Y/%m/%d %I:%M %psame, but with / separator The date function supports the following formats. It returns a DateTime object with the time set to midnight in the specified timezone (or the local timezone if no timezone is specified). FormatExamples %Y-%m-%d2024-02-10 2024-02-10 +0100 2024-02-10 Europe/Berlin %Y/%m/%d2024/02/10 2024/02/10 +0100 2024/02/10 Europe/Berlin The time function supports the following formats. It returns a DateTime object with the date set to the current date. If no timezone is specified, the local timezone is used. FormatExamples %H:%M:%S%.f12:30:00 06:30:00 -0600 07:30:00 US/Eastern 12:30:00.123456 %H:%M12:30 06:30 -0600 07:30 US/Eastern %I:%M:%S%.f %p12:30:00 PM 06:30:00 AM -0600 07:30:00 AM US/Eastern 12:30:00.123456 PM %I:%M %p12:30 PM 06:30 AM -0600 07:30 AM US/Eastern # Printing, testing, debugging ## Printing Numbat has a builtin print procedure that can be used to print the value of an expression: print(2 km/h) print(3 ft < 1 m)  You can also print out simple messages as strings. This is particularly useful when combined with string interpolation to print results of a computation: let radius: Length = sqrt(footballfield / 4 pi) -> meter print("A football field would fit on a sphere of radius {radius}")  You can use almost every expression inside a string interpolation field. For example: print("3² + 4² = {hypot2(3, 4)}²") let speed = 25 km/h print("Speed of the bicycle: {speed} ({speed -> mph})")  Format specifiers are also supported in interpolations. For instance: print("{pi:0.2f}") // Prints "3.14"  For more information on supported format specifiers, please see this page. ## Testing The assert_eq procedure can be used to test for (approximate) equality of two quantities. This is often useful to make sure that (intermediate) results in longer calculations have a certain value, e.g. when restructuring the code. The general syntax is assert_eq(q1, q2) assert_eq(q1, q2, ε)  where the first version tests for exact equality while the second version tests for approximate equality $$|q_1-q_2| < \epsilon$$ with a specified accuracy of $$\epsilon$$. For example: assert_eq(2 + 3, 5) assert_eq(1 ft × 77 in², 4 gal) assert_eq(alpha, 1 / 137, 1e-4) assert_eq(3.3 ft, 1 m, 1 cm)  There is also a plain assert procedure that can test any boolean condition. For example: assert(1 yard < 1 meter) assert(π != 3)  A runtime error is thrown if an assertion fails. Otherwise, nothing happens. ## Debugging You can use the builtin type procedure to see the type (or physical dimension) of a quantity: >>> type(g0) Length / Time² >>> type(2 < 3) Bool  # Structs Numbat has compound data structures in the form of structs: struct Vector { x: Length, y: Length, } let origin = Vector { x: 0 m, y: 0 m } let position = Vector { x: 6 m, y: 8 m } # A function with a struct as a parameter fn euclidean_distance(a: Vector, b: Vector) = sqrt((a.x - b.x)² + (a.y - b.y)²) assert_eq(euclidean_distance(origin, position), 10 m) # Struct fields can be accessed using .field notation let x = position.x  # Advanced This chapter covers more advanced topics, like defining custom physical units or new physical dimensions. # Dimension definitions New (physical) dimensions can be introduced with the dimension keyword. Similar like for units, there are base dimensions (like length, time and mass) and dimensions that are derived from those base dimensions (like momentum, which is mass · length / time). Base dimensions are simply introduced by declaring their name: dimension Length dimension Time dimension Mass  Derived dimensions need to specify their relation to base dimensions (or other derived dimensions). For example: dimension Velocity = Length / Time dimension Momentum = Mass * Velocity dimension Force = Mass * Acceleration = Momentum / Time dimension Energy = Momentum^2 / Mass = Mass * Velocity^2 = Force * Length  In the definition of Force and Energy, we can see that multiple alternative definitions can be specified. This is entirely optional. When given, the compiler will make sure that all definitions are equivalent. # Unit definitions New units of measurement can be introduced with the unit keyword. There are two types of units: base units and derived units. A new base unit can be defined by specifying the physical dimension it represents. For example, in the International System of Units (SI), the second is the base unit for measuring times: unit second: Time  Here, Time denotes the physical dimension. To learn more, you can read the corresponding chapter. But for now, we can just assume that they are already given. Derived units are also introduced with the unit keyword. But unlike base units, they are defined through their relation to other units. For example, a minute can be defined as unit minute: Time = 60 second  Here, the : Time annotation is optional. If a dimension is specified, it will be used to verify that the right hand side expression (60 second) is indeed of physical dimension Time. This is apparent in this simple example, but can be useful for more complicated unit definitions like unit farad: Capacitance = ampere^2 second^4 / (kilogram meter^2)  ## Prefixes If a unit may be used with metric prefixes such as milli/m, kilo/k or mega/M, we can prepend the unit definition with the @metric_prefixes decorator: @metric_prefixes unit second: Time  This allows identifiers such as millisecond to be used in calculations. See the section below how prefixes interact with aliases. Similarly, if a unit should be prependable with binary (IEC) prefixes such as kibi/Ki, mebi/Mi or gibi/Gi, you can add the @binary_prefixes decorator. A unit might also allow for both metric and binary prefixes, for example: @binary_prefixes @metric_prefixes unit byte = 8 bit  This allows the usage of both mebibyte (1024² byte) as well as megabyte (1000² byte). ## Aliases It is often useful to define alternative names for a unit. For example, we might want to use the plural form seconds or the commonly used short version s. We can use the @aliases decorator to specify them: @metric_prefixes @aliases(meters, metre, metres, m: short) unit meter: Length  In addition to the name, we can also specify how aliases interact with prefixes using : long (the default), : short, : both or : none. The actual unit name (meter) and all long aliases will accept the long version of prefixes (…, milli, kilo, mega, giga, …). All short aliases (m in the example above) will only accept the respective short versions of the prefixes (…, m, k, M, G, …). Aliases annotated with : both or : none accept either both long and short prefixes, or none of them. The unit definition above allows all of following expressions: millimeter kilometer millimeters kilometers millimetre kilometre millimetres kilometres mm km ...  ## Ad-hoc units It is often useful to introduce ‘fictional’ physical units (and dimensions). This comes up frequently when you want to count things. For example: unit book @aliases(pages) unit page @aliases(words) unit word let words_per_book = 500 words/page × 300 pages/book  Note that those base unit definitions will implicitly create new dimensions which are capitalized versions of the unit names (Book, Page, Word). A definition like unit book is a shorthand for dimension Book; unit book: Book. Those units now allow us to count books, pages and words independently without any risk of mixing them. The words_per_book constant in this examples has a type of Word / Book. Another example shows how we introduce a dot unit to do calculations with screen resolutions: @aliases(dots) unit dot unit dpi = dots / inch # Note: a Dot dimension was implicitly created for us fn inter_dot_spacing(resolution: Dot / Length) -> Length = 1 dot / resolution inter_dot_spacing(72 dpi) -> µm # 353 µm  # Syntax overview # This is a line comment. It can span over # multiple lines # 1. Imports use prelude # This is not necessary. The 'prelude' # module will always be loaded upon startup use units::stoney # Load a specific module # 2. Numbers 12345 # integer notation 12_345 # optional decimal separators 0.234 # floating point notation .234 # without the leading zero 1.234e15 # scientific notation 1.234e+15 1e-9 1.0e-9 0x2A # hexadecimal 0o52 # octal 0b101010 # binary NaN # Not a number inf # Infinity # 3. Simple expressions 3 + (4 - 3) # Addition and subtraction 1920 / 16 * 9 # Multiplication, division 1920 ÷ 16 × 9 # Unicode-style, '·' is also multiplication 2 pi # Whitespace is implicit multiplication meter per second # 'per' keyword can be used for division 2^3 # Exponentiation 2**3 # Python-style 2³ # Unicode exponents 2^-3 # Negative exponents mod(17, 4) # Modulo 3 in -> cm # Unit conversion, can also be → or ➞ 3 in to cm # Unit conversion with the 'to' keyword cos(pi/3 + pi) # Call mathematical functions pi/3 + pi // cos # Same, 'arg // f' is equivalent to 'f(arg)' # The '//' operator has the lowest precedence # which makes it very useful for interactive # terminals (press up-arrow, and add '// f') # 4. Constants let n = 4 # Simple numerical constant let q1 = 2 m/s # Right hand side can be any expression let q2: Velocity = 2 m/s # With optional type annotation let q3: Length / Time = 2 m/s # more complex type annotation # 5. Function definitions fn foo(z: Scalar) -> Scalar = 2 * z + 3 # A simple function fn speed(len: Length, dur: Time) -> Velocity = len / dur # Two parameters fn my_sqrt<T: Dim>(q: T^2) -> T = q^(1/2) # A generic function fn is_non_negative(x: Scalar) -> Bool = x ≥ 0 # Returns a bool # 6. Dimension definitions dimension Fame # A new base dimension dimension Deceleration = Length / Time^2 # A new derived dimension # 7. Unit definitions @aliases(quorks) # Optional aliases-decorator unit quork = 0.35 meter # A new derived unit @metric_prefixes # Optional decorator to allow 'milliclonk', etc. @aliases(ck: short) # short aliases can be used with short prefixes (mck) unit clonk: Time = 0.2 seconds # Optional type annotation @metric_prefixes @aliases(wh: short) unit warhol: Fame # New base unit for the "Fame" dimension unit thing # New base unit with automatically generated # base dimension "Thing" # 8. Conditionals fn bump(x: Scalar) -> Scalar = # The construct 'if <cond> then <expr> else <expr>' if x >= 0 && x <= 1 # is an expression, not a statement. It can span then 1 # multiple lines. else 0 # 9. Procedures print(2 kilowarhol) # Print the value of an expression print("hello world") # Print a message print("value of pi = {pi}") # String interpolation print("sqrt(10) = {sqrt(10)}") # Expressions in string interpolation print("value of π ≈ {π:.3}") # Format specifiers assert(1 yard < 1 meter) # Assertion assert_eq(1 ft, 12 in) # Assert that two quantities are equal assert_eq(1 yd, 1 m, 10 cm) # Assert that two quantities are equal, up to # the given precision type(2 m/s) # Print the type of an expression # 10. Structs struct Element { # Define a struct name: String, atomic_number: Scalar, density: MassDensity, } let hydrogen = Element { # Instantiate it name: "Hydrogen", atomic_number: 1, density: 0.08988 g/L, } hydrogen.density # Access the field of a struct  # The prelude Numbat comes with a special module called prelude that is always loaded on startup (unless --no-prelude is specified on the command line). This module is split into multiple submodules and sets up a useful default environment with mathematical functions, constants but also dimension definitions, unit definitions and physical constants. You can find the full source code of the standard library on GitHub. This chapter is a reference to the prelude module. # Predefined functions ## Utility fn unit_of<T>(x: T) -> T fn value_of<T>(x: T) -> Scalar fn is_nan<T>(x: T) -> Bool fn is_infinite<T>(x: T) -> Bool  ## Math ### Basics fn abs<T>(x: T) -> T fn round<T>(x: T) -> T fn floor<T>(x: T) -> T fn ceil<T>(x: T) -> T fn mod<T>(a: T, b: T) -> T fn sqrt<D>(x: D^2) -> D fn sqr<D>(x: D) -> D^2  ### Exponential and logarithm fn exp(x: Scalar) -> Scalar fn ln(x: Scalar) -> Scalar fn log(x: Scalar) -> Scalar fn log10(x: Scalar) -> Scalar fn log2(x: Scalar) -> Scalar  ### Trigonometry Basic: fn cos(x: Scalar) -> Scalar fn sin(x: Scalar) -> Scalar fn tan(x: Scalar) -> Scalar fn asin(x: Scalar) -> Scalar fn acos(x: Scalar) -> Scalar fn atan(x: Scalar) -> Scalar fn atan2<T>(y: T, x: T) -> Scalar  Hyperbolic: fn sinh(x: Scalar) -> Scalar fn cosh(x: Scalar) -> Scalar fn tanh(x: Scalar) -> Scalar fn asinh(x: Scalar) -> Scalar fn acosh(x: Scalar) -> Scalar fn atanh(x: Scalar) -> Scalar  Extra: fn cot(x: Scalar) -> Scalar fn acot(x: Scalar) -> Scalar fn coth(x: Scalar) -> Scalar fn acoth(x: Scalar) -> Scalar fn secant(x: Scalar) -> Scalar fn arcsecant(x: Scalar) -> Scalar fn cosecant(x: Scalar) -> Scalar fn csc(x: Scalar) -> Scalar fn acsc(x: Scalar) -> Scalar fn sech(x: Scalar) -> Scalar fn asech(x: Scalar) -> Scalar fn csch(x: Scalar) -> Scalar fn acsch(x: Scalar) -> Scalar  ### Others fn gamma(x: Scalar) -> Scalar  ### Statistics fn mean<D>(xs: D…) -> D fn maximum<D>(xs: D…) -> D fn minimum<D>(xs: D…) -> D  ### Geometry fn hypot2<T>(x: T, y: T) -> T fn hypot3<T>(x: T, y: T, z: T) -> T fn circle_area<L>(radius: L) -> L^2 fn circle_circumference<L>(radius: L) -> L fn sphere_area<L>(radius: L) -> L^2 fn sphere_volume<L>(radius: L) -> L^3  ### Random sampling fn random() -> Scalar fn rand_uniform<T>(a: T, b: T) -> T fn rand_int<T>(a: T, b: T) -> T fn rand_normal<T>(μ: T, σ: T) -> T fn rand_bernoulli(p: Scalar) -> Scalar fn rand_binomial(n: Scalar, p: Scalar) -> Scalar fn rand_geometric(p: Scalar) -> Scalar fn rand_poisson<T>(λ: T) -> T fn rand_exponential<T>(λ: T) -> 1/T fn rand_lognormal(μ: Scalar, σ: Scalar) -> Scalar fn rand_pareto<T>(α: Scalar, min: T) -> T  ### Algebra # Returns the solutions of the equation a x² + b x + c = 0 quadratic_equation<A: Dim, B: Dim>(a: A, b: B, c: B² / A) -> List<B / A>  ## Date and time See this page for details. ## Physics ### Temperature conversion fn from_celsius(t_celsius: Scalar) -> Temperature fn celsius(t_kelvin: Temperature) -> Scalar fn from_fahrenheit(t_fahrenheit: Scalar) -> Temperature fn fahrenheit(t_kelvin: Temperature) -> Scalar  ## Chemistry # Get properties of a chemical element by its symbol or name (case-insensitive). fn element(pattern: String) -> ChemicalElement  ## Strings fn str_length(s: String) -> Scalar fn str_slice(s: String, start: Scalar, end: Scalar) -> String fn str_append(a: String, b: String) -> String fn str_contains(haystack: String, needle: String) -> Bool fn str_replace(s: String, pattern: String, replacement: String) -> String fn str_repeat(a: String, n: Scalar) -> String fn chr(n: Scalar) -> String fn hex(n: Scalar) -> String  # Constants ## Mathematical • pi, π • τ • e • golden_ratio, φ ## Named numbers Large numbers • hundred • thousand • million • billion • trillion • quadrillion • quintillion • googol Unicode fractions: • ½, ⅓, ⅔, ¼, ¾, … Colloquial: • quarter • half • semi • double • triple • dozen ## Physics DescriptionIdentifierDimension The speed of light in vacuumspeed_of_light, cVelocity The Newtonian constant of gravitationgravitational_constant, GForce × Length^2 / Mass^2 Standard acceleration of gravity on earthgravity, g0Acceleration The Planck constantplanck_constant, ℎMass × Length^2 / Time The reduced Planck constanth_bar, ℏMass × Length^2 / Time Mass of the electronelectron_massMass Elementary charge (charge of the electron)elementary_charge, electron_chargeElectricCharge Magnetic constant (vacuum magnetic permeability)magnetic_constant, µ0, mu0Force / Current^2 Electric constant (vacuum electric permittivity)electric_constant, ε0, eps0Capacitance / Length Bohr magnetonbohr_magneton, µ_BEnergy / MagneticFluxDensity Fine structure constantfine_structure_constant, alpha, αScalar Proton massproton_massMass Neutron massneutron_massMass Avogadro constantavogadro_constant, N_A1 / AmountOfSubstance Boltzmann constantboltzmann_constant, k_BEnergy / Temperature Stefan-Boltzmann constantstefan_boltzmann_constantPower / (Area × Temperature^4) Ideal gas constantgas_constant, REnergy / (AmountOfSubstance × Temperature) Planck lengthplanck_lengthLength Planck massplanck_massMass Planck timeplanck_timeTime Planck temperatureplanck_temperatureTemperature Planck energyplanck_energyEnergy Bohr radiusbohr_radius, a0Length Rydberg constantrydberg_constantWavenumber # List of supported units See also: Unit notation. All SI-accepted units support metric prefixes (mm, cm, km, … or millimeter, centimeter, kilometer, …) and — where sensible — units allow for binary prefixes (MiB, GiB, … or mebibyte, gibibyte, …). DimensionUnit nameIdentifier(s) AbsorbedDoseGraygray, grays, Gy ActivityBecquerelbecquerel, becquerels, Bq AmountOfSubstanceMolemol, mole, moles AngleMinute of arcarcmin, arcminute, arcminutes AngleSecond of arcarcsec, arcsecond, arcseconds AngleDegreedeg, degree, degrees, ° AngleGradiangon, gons, grad, grade, grades, gradian, gradians, grads AngleRadianrad, radian, radians AngleRevolutionrev, revolution, revolutions AngleTurnturn, turns AreaAcreacre, acres AreaAreare AreaBarnbarn, barns AreaFootball fieldfootballfield AreaHectareha, hectare, hectares BeatBeatbeat, beats Beat / TimeBeats per minutebpm, BPM CapacitanceFaradF, farad, farads CatalyticActivityKatalkat, katal, katals CurrentAmpereA, ampere, amperes DataRateBits per secondbps DigitalInformationBitbit, bits DigitalInformationByteB, byte, Byte, bytes, Bytes, octet, Octet, octets, Octets DotDotdot, dots Dot / LengthDots per inchdpi DynamicViscosityPoisepoise ElectricChargeAmpere-hourAh, amperehour ElectricChargeCoulombC, coulomb, coulombs ElectricConductanceSiemensS, siemens ElectricResistanceOhmohm, ohms, Ω, Ω EnergyBritish thermal unitBTU, Btu EnergyCaloriecal, calorie, calories EnergyElectron voltelectronvolt, electronvolts, eV EnergyErgerg, ergs EnergyHartreehartree, hartrees EnergyJouleJ, joule, joules EnergyPlanck energyplanck_energy EnergyRydberg unit of energyRy EnergyWatt-hourwatthour, Wh EquivalentDoseSievertsievert, sieverts, Sv ForceDynedyn, dyne ForceKilogram-forcekgf, kilogram_force ForceNewtonN, newton, newtons ForceOunce-forceounce_force, ozf ForcePound-forcelbf, pound_force Force / VolumeMercuryHg FrameFrameframe, frames Frame / TimeFrames per secondfps FrequencyHertzhertz, Hz FrequencyRevolutions per minuterpm, RPM IlluminanceFoot-candlefc, footcandle, footcandles IlluminanceLuxlux, lx InductanceHenryH, henries, henry, henrys KinematicViscosityStokesSt, stokes LengthÅngströmangstrom, angstroms, Å, Å LengthAstronomical unitastronomicalunit, astronomicalunits, au, AU LengthBohrbohr LengthFathomfathom, fathoms LengthFermifermi LengthFootfeet, foot, ft LengthFurlongfurlong, furlongs LengthInchin, inch, inches LengthLeagueleague, leagues LengthLight-yearlightyear, lightyears, ly LengthMetrem, meter, meters, metre, metres LengthMicronmicron LengthMilemi, mile, miles LengthNautical Milenautical_mile, nautical_miles, NM, nmi LengthParsecparsec, parsecs, pc LengthPlanck lengthplanck_length LengthRack unitrackunit, rackunits, RU, U LengthUS rodperch, rod, rods LengthSmootsmoot LengthStoney lengthstoney_length LengthThousandth of an inchmil, mils, thou LengthYardyard, yards, yd Length / VolumeMiles per gallonmpg Length^2darcydarcies, darcy, darcys LuminousFluxLumenlm, lumen, lumens LuminousIntensityCandelacandela, candelas, cd MagneticFieldStrengthOerstedOe, oersted MagneticFluxMaxwellmaxwell, Mx MagneticFluxWeberWb, weber, webers MagneticFluxDensityGaussgauss MagneticFluxDensityTeslaT, tesla, teslas MassDaltonDa, dalton, daltons MassFirkinfirkin, firkins MassGraingrain, grains MassGramg, gram, gramme, grammes, grams MassHundredweightcwt, long_hundredweight MassLong tonlong_ton, long_tons MassOunceounce, ounces, oz MassPlanck massplanck_mass MassPoundlb, lbs, pound, pounds MassStonestone MassStoney massstoney_mass MassTonnemetricton, ton, tonne, tonnes, tons MolalityMolalmolal MolarityMolarmolar MoneyAustralian dollarA$, AUD, australian_dollar, australian_dollars
MoneyBrazilian realbrazilian_real, brazilian_reals, BRL, R$ MoneyPound sterlingbritish_pound, GBP, pound_sterling, £ MoneyBulgarian levBGN, bulgarian_lev, bulgarian_leva MoneyCanadian dollarC$, CAD, canadian_dollar, canadian_dollars
MoneyCzech korunaczech_koruna, czech_korunas, CZK, Kč
MoneyDanish kronedanish_krone, danish_kroner, DKK
MoneyUS dollar$, dollar, dollars, USD MoneyEuroEUR, euro, euros, € MoneyHong Kong dollarHK$, HKD, hong_kong_dollar, hong_kong_dollars
MoneyHungarian forintFt, HUF, hungarian_forint, hungarian_forints
MoneyIcelandic krónaicelandic_krona, icelandic_kronur, icelandic_króna, icelandic_krónur, ISK
MoneyIndian rupeeindian_rupee, indian_rupees, INR, ₹
MoneyIndonesian rupiahIDR, indonesian_rupiah, indonesian_rupiahs, Rp
MoneyIsraeli new shekelILS, israeli_new_shekel, israeli_new_shekels, NIS, ₪
MoneyMalaysian ringgitmalaysian_ringgit, malaysian_ringgits, MYR, RM
MoneyNew Zealand dollarnew_zealand_dollar, new_zealand_dollars, NZ$, NZD MoneyNorwegian kroneNOK, norwegian_krone, norwegian_kroner MoneyPhilippine pesophilippine_peso, philippine_pesos, PHP, ₱ MoneyPolish złotyPLN, polish_zloty, polish_zlotys, zł MoneyChinese yuanCNY, renminbi, yuan, 元 MoneyRomanian leulei, romanian_leu, romanian_leus, RON MoneySingapore dollarS$, SGD, singapore_dollar, singapore_dollars
MoneySouth African randsouth_african_rand, ZAR
MoneySouth Korean wonKRW, south_korean_won, south_korean_wons, ₩
MoneySwedish kronaSEK, swedish_krona, swedish_kronor
MoneySwiss francCHF, swiss_franc, swiss_francs
MoneyThai bahtthai_baht, thai_bahts, THB, ฿
MoneyTurkish liraTRY, turkish_lira, turkish_liras, ₺
MoneyJapanese yenJPY, yen, ¥, 円
PersonPersoncapita, people, person, persons
PiecePiecepiece, pieces
PixelPixelpixel, pixels, px
Pixel / LengthPixels per inchppi
PowerMetric horsepowerhorsepower, hp
PowerWattW, watt, watts
PressureStandard atmosphereatm, atmosphere, atmospheres
PressureBarbar, bars
PressureInch of mercuryinHg
PressureKilopound-force per square inchksi, KSI
PressureMillimeter of mercurymmHg
PressureMegapound-force per square inchmpsi, MPSI
PressurePascalPa, pascal, pascals
PressurePound-force per square inchpsi, PSI
PressureTorrtorr
ScalarBillionbillion
ScalarDozendozen
ScalarHundredhundred
ScalarMillionmillion
ScalarParts per billionpartsperbillion, ppb
ScalarParts per millionpartspermillion, ppm
ScalarParts per quadrillionpartsperquadrillion, ppq
ScalarParts per trillionpartspertrillion, ppt
ScalarPercent%, pct, percent
ScalarPermillepermil, permill, permille, ‰
ScalarQuadrillionquadrillion
ScalarQuintillionquintillion
ScalarThousandthousand
ScalarTrilliontrillion
SolidAngleSteradiansr, steradian, steradians
TemperatureKelvinK, kelvin, kelvins
TemperaturePlanck temperatureplanck_temperature
TimeCenturycenturies, century
TimeDayd, day, days
TimeDecadedecade, decades
TimeFortnightfortnight, fortnights
TimeGregorian yeargregorian_year, gregorian_years
TimeHourh, hour, hours, hr
TimeJulian yearjulian_year, julian_years
TimeMillenniummillennia, millennium
TimeMinutemin, minute, minutes
TimeMonthmonth, months
TimePlanck timeplanck_time
TimeSeconds, sec, second, seconds
TimeSidereal daysidereal_day, sidereal_days
TimeStoney timestoney_time
TimeWeekweek, weeks
TimeTropical yeartropical_year, tropical_years, year, years, yr
VelocityKnotkn, knot, knots, kt
VelocityKilometres per hourkph
VelocityMiles per hourmph
VoltageVoltV, volt, volts
VolumeCubic centimetrecc, ccm
VolumeUS cupcup, cups
VolumeUS fluid ouncefloz, fluidounce, fluidounces
VolumeUS liquid gallongal, gallon, gallons
VolumeUS hogsheadhogshead, hogsheads
VolumeImperial Bushelimperial_bushel, imperial_bushels, UK_bu
VolumeImperial Fluid Drachmimperial_fluid_drachm, imperial_fluid_drachms, UK_fldr
VolumeImperial Fluid Ounceimperial_fluidounce, imperial_fluidounces, UK_floz
VolumeImperial Gallonimperial_gallon, imperial_gallons, UK_gal
VolumeImperial Gillimperial_gill, imperial_gills, UK_gi
VolumeImperial Pintimperial_pint, imperial_pints, UK_pt
VolumeImperial Quartimperial_quart, imperial_quarts, UK_qt
VolumeLitrel, L, liter, liters, litre, litres
VolumeUS liquid pintpint, pints
VolumeSwimming poolswimmingpool
VolumeUS tablespoontablespoon, tablespoons, tbsp
VolumeUS teaspoonteaspoon, teaspoons, tsp

# Installation

## Linux

### Ubuntu

… and other Debian-based Linux distributions.

Download the latest .deb package from the release page and install it via dpkg. For example:

curl -LO https://github.com/sharkdp/numbat/releases/download/v1.12.0/numbat_1.12.0_amd64.deb
sudo dpkg -i numbat_1.12.0_amd64.deb


Alternatively, if you want automatic updates, you can use a community-maintained Numbat PPA. The PPA only hosts packages for the amd64/x86_64 architecture.

sudo add-apt-repository ppa:apandada1/numbat
sudo apt update
sudo apt install numbat


### Arch Linux

In Arch Linux and Arch based distributions, you can install the prebuilt package of Numbat from the AUR for the x86_64 architecture:

yay -S numbat-bin


You can also install the numbat AUR package, which will download the source and compile it. It works on all architectures.

yay -S numbat


### Void Linux

You can install the numbat package using

sudo xbps-install -S numbat


### Chimera Linux

Chimera Linux has a numbat package in its contrib repo. Enable it if you haven’t already, then install numbat:

doas apk add numbat


## macOS

### Homebrew

You can install Numbat with Homebrew:

brew install numbat



## Windows

### Scoop

You can install the numbat package using scoop:

scoop install main/numbat


## NixOS

… or any distribution where Nix is installed.

nix-env -iA nixpkgs.numbat


environment.systemPackages = [
pkgs.numbat
];


## From pre-built binaries

Download the latest release for your system from this page. Unpack the archive and place the numbat/numbat.exe binary in a folder that is on your PATH.

Note that the modules folder that is included in the archives is not strictly required to run Numbat. It serves more as a reference for interested users. However, if you want to get the best possible experience or if you are a package maintainer, please follow these guidelines.

## From source

Clone the Git repository, and build Numbat with cargo:

git clone https://github.com/sharkdp/numbat
cd numbat/
cargo install -f --path numbat-cli


Or install the latest release using

cargo install numbat-cli


## Guidelines for package maintainers

Thank you for packaging Numbat! This section contains instructions that are not strictly necessary to create a Numbat package, but provide users with the best-possible experience on your target platform.

Numbat has a standard library that is written in Numbat itself. The sources for this so called “prelude” are available in the numbat/modules folder. We also include this modules folder in the pre-built GitHub releases. Installing this folder as part of the package installation is not necessary for Numbat to work, as the prelude is also stored inside the numbat binary. But ideally, this folder should be made available for users. There are three reasons for this:

• Users might want to look at the code in the standard library to get a better understanding of the language itself.
• For some error messages, Numbat refers to locations in the source code. For example, if you type let meter = 2, the compiler will let you know that this identifier is already in use, and has been previously defined at a certain location inside the standard library. If the corresponding module is available as a file on the users system, they will see the proper path and can read the corresponding file.
• Users might want to make changes to the prelude. Ideally, this should be done via a user module folder, but the system-wide folder can serve as a template.

In order for this to work, the modules folder should ideally be placed in the standard location for the target operating system. If this is not possible, package maintainers can customize numbat during compilation by setting the environment variable NUMBAT_SYSTEM_MODULE_PATH to the final locatiom. If this variable is set during compilation, the specified path will be compiled into the numbat binary.

In order to test that everything is working as intended, you can open numbat and type let meter = 2. The path in the error message should point to the specified location (and not to <builtin>/…).

If your OS uses .desktop files, you should probably also install:

• assets/numbat.desktop (typically to /usr/share/applications)
• assets/numbat.svg (typically to /usr/share/icons/hicolor/scalable/apps)
• assets/numbat-*x*.png (typically to e.g. /usr/share/icons/hicolor/32x32/apps, depending on each icon’s size)

This allows users to e.g. pin Numbat to GNOME’s Dash.

# Usage

## Modes

You can run the Numbat command-line application in three different modes:

ModeCommand to run
Start an interactive session (REPL)numbat
Run a Numbat programnumbat script.nbt
Evaluate a single expressionnumbat -e '30 km/h -> mi/h'

## Command-line options

See numbat --help for more information.

## Interactive sessions

Interactive sessions allow you to perform a sequence of calculations. You can use the special identifiers ans or _ to refer to the result of the last calculation. For example:

>>> 60 kW h / 150 kW

= 0.4 h

>>> ans -> minutes

= 24 min


### Commands

There is a set of special commands that only work in interactive mode:

CommandAction
list, lsList all functions, dimensions, variables and units
list <what>Where <what> can be functions, dimensions, variables, units
info <identifier>Get more information about units, variables and functions
clearClear screen
help, ?View short help text
quit, exitQuit the session

### Key bindings

In interactive command-line mode, you can use the following key bindings. Most importantly, Tab for auto-completion, arrow keys and Ctrl-R for browsing the command history, and Ctrl-D for exiting the interactive session.

Key sequenceAction
Tab, Ctrl-IAuto-completion
Ctrl-DQuit
Ctrl-LClear screen
Up, DownBrowse command history
Ctrl-RSearch command history
Ctrl-CClear the current line
Alt-EnterInsert newline
Home, Ctrl-AMove cursor to the beginning of the line
End, Ctrl-EMove cursor to the end of the line
Ctrl-WDelete word leading up to cursor

# Customization

## Startup

By default, Numbat will load the following modules/files during startup, in order:

• Numbat Prelude (a module called prelude, either from <module-path>/prelude.nbt if available, or the builtin version)
• The user initialization file, if available (a file called init.nbt from <config-path>/init.nbt)

### Config path

Numbat’s configuration folder (<config-path> above) can be found under:

PlatformPath
Linux$HOME/.config/numbat or $XDG_CONFIG_HOME/numbat
macOS$HOME/Library/Application Support/numbat WindowsC:\Users\Alice\AppData\Roaming\numbat ## Module paths Numbat will load modules from the following sources. Entries higher up in the list take precedence. LocationDescription $NUMBAT_MODULES_PATHThis environment variable can point to a
single directory or contain a :-separated
list of paths
<config-path>/modulesUser-customized module folder
/usr/share/numbat/modulesSystem-wide module folder (Linux and macOS)
C:\Program Files\numbat\modulesSystem-wide module folder (Windows)
<builtin>Builtin modules inside the numbat binary

Note that the System-location might be different for some installation methods. Refer to your package manager for details.

## Customization

### Configuration

Numbat’s configuration file is called config.toml, and it needs to be placed in <config-path> described above (~/.config/numbat/config.toml on Linux). You can generate a default configuration by calling

numbat --generate-config


The most important fields are:

# Controls the welcome message. Can be "long", "short", or "off".
intro-banner = "long"

# Controls the prompt character(s) in the interactive terminal.
prompt = ">>> "

# Whether or not to pretty-print expressions before showing the result.
# Can be "always", "never" or "auto". The latter uses pretty-printing
# only in interactive mode.
pretty-print = "auto"

[exchange-rates]
# When and if to load exchange rates from the European Central Bank for
# currency conversions. Can be "on-startup" to always fetch exchange rates
# in the background when the application is started. With "on-first-use",
# Numbat only fetches exchange rates when they are needed. Exchange rate
# fetching can also be disabled using "never". The latter will lead to
# "unknown identifier" errors when a currency unit is being used.
fetching-policy = "on-startup"


### Custom functions, constants, units

If you want to add custom functions, constants, or units to your default environment, create a init.nbt file in your config folder (~/.config/numbat/init.nbt on Linux).

### Custom modules

You can also create your own modules that can be loaded on demand. To this end, create a new file, say <module-path>/user/finance.nbt in one of the module folders (e.g. ~/.config/numbat/modules/custom/finance.nbt on Linux). This module can then be loaded using

use custom::finance


in your Numbat scripts or in the REPL. You can also load custom modules from init.nbt if you want to have them available all the time.

You can also organize modules into subfolders (e.g. <module-path>/custom/finance/functions.nbt). In that case, you can load them using

use custom::finance::functions


In fact, the custom folder is just a convention to avoid name clashes with the standard library.

# Usage

The browser-based version of Numbat is available at https://numbat.dev/.

## Interactive terminal

The terminal allows you to perform a sequence of calculations. You can use the arrow keys to browse through the command history. The special identifiers ans and _ refer to the result of the last calculation. For example:

>>> 60 kW h / 150 kW

= 0.4 h

>>> ans -> minutes

= 24 min


## Commands

There is a set of special commands that only work in the web version:

CommandAction
list, lsList all constants, units, and dimensions
list <what>Where <what> can be functions, dimensions, variables, units
info <identifier>Get more information about units and variables
help, ?View short help text
resetReset state (clear constants, functions, units, …)
clearClear screen

## Key bindings

In interactive command-line mode, you can use the following key bindings. Most importantly, Tab for auto-completion, arrow keys and Ctrl-R for browsing the command history, and Ctrl-D for exiting the interactive session.

Key sequenceAction
TabAuto-completion
Ctrl-LClear screen
Up, DownBrowse command history
Ctrl-RSearch command history
Ctrl-CClear the current line
Shift-EnterInsert newline
Home, Ctrl-AMove cursor to the beginning of the line
End, Ctrl-EMove cursor to the end of the line
Ctrl-Left, Ctrl-RightMove cursor one word left/right
Ctrl-KRemove text to the right of the cursor
Ctrl-URemove text to the left of the cursor

## Sharing calculations

To share the result of a calculation with someone else, you can just copy the URL from your browers address bar. As you enter new lines in the terminal, your input will be appended to the URL to build up something like https://numbat.dev/?q=let+P0+%3D+50_000+people%0A… that you can just copy and share. To reset the state and clear the URL, use the reset command (see above).

# Type system

Numbat is a language with a special type system that treats physical dimensions as types. A type checker infers types for every expression in the program and ensures that everything is correct in terms of physical dimensions, which implies correctness in terms of physical units. For example, the expression 2 meter has a type of Length. The expression 3 inch also has a type of Length. The combined expression 2 meter + 3 inch is therefore well-typed. On the other hand, 2 meter + 3 second is ill-typed, as 3 second is of type Time.

The type system is static which means that the correctness of a Numbat program is verified before the program starts executing. Note that certain runtime errors (like division-by-zero) can still occur.

## Algebra of types

Types in Numbat can be combined in various ways to produce new types. In its most general form, a type can be thought of as a product of physical (base) dimensions $$D_k$$ with exponents $$\alpha_k \in \mathbb{Q}$$: $\prod_k D_k^{\alpha_k}$ For example, the type Energy can be represented as Mass¹ × Length² × Time⁻².

### Multiplication

This naturally allows us to multiply types (by combining the factors of both products into a single product). We can use the * operator to construct types for physical dimensions that are products of two or more (base) dimensions. For example:

dimension Time
dimension Current
dimension ElectricCharge = Current * Time


### Exponentiation

We can also raise units to arbitrary powers $$n \in \mathbb{Q}$$, by simply multiplying each $$\alpha_k$$ with $$n$$. The syntax uses the ^ exponentiation operator:

dimension Length
dimension Volume = Length^3

dimension Time
dimension Frequency = Time^(-1)


### Division

Once we have multiplication and exponentiation, we can define the division of two types as

TypeA / TypeB ≡ TypeA * TypeB^(-1)


This is mostly for convenience. It allows us to write definitions like

dimension Power = Energy / Time


Note: When we talk about products of types in this section, we mean actual, literal products. Type theory also has the notion of product types which denote something else: compound types — like tuples or structs — that are built by combining two or more types. If we think of types in terms of the sets of all possible values that they represent, then product types represent the Cartesian product of those.

## Type inference and type annotations

The type checker can infer the types of (most) expressions without explicitly declaring them. For example, the following definition does not mention any types:

let E_pot = 80 kg × 9.8 m/s² × 5 m


However, it is often helpful to specify the type anyway. This way, we can make sure that no mistakes were made:

let E_pot: Energy = 80 kg × 9.8 m/s² × 5 m


The type checker will compare the inferred type with the specified type and raise an error in case of inconsistency.

Function definitions also allow for type annotations, both for the parameters as well as the return type. The following example shows a function that takes a quantity of type Length and returns a Pressure:

let p0: Pressure = 101325 Pa
let t0: Temperature = 288.15 K

let gradient = 0.65 K / 100 m

fn air_pressure(height: Length) -> Pressure = p0 · (1 - gradient · height / t0)^5.255


## Generic types

Numbat’s type system also supports generic types (type polymorphism). These can be used for functions that work regardless of the physical dimension of the argument(s). For example, the type signature of the absolute value function is given by

fn abs<D>(x: D) -> D


where the angle brackets after the function name introduce new type parameters (D). This can be read as: abs takes an arbitrary physical quantity of dimension D and returns a quantity of the same physical dimension D.

As a more interesting example, we can look at the sqrt function. Its type signature can be written as

fn sqrt<D>(x: D^2) -> D


Alternatively, it could also be specified as fn sqrt<D>(x: D) -> D^(1/2).

## Limitations

The static type system also has some limitations. Let’s look at an exponentiation expression like

expr1 ^ expr2


where expr1 and expr2 are arbitrary expressions. In order for that expression to properly type check, the type of expr2 must be Scalar — something like 2^meter does not make any sense. If the type of expr1 is also Scalar, everything is well and the type of the total expression is also Scalar. An example for this trivial case is an expression like e^(-x²/σ²). As long as the type of x is the same as the type of σ, this is fine.

A more interesting case arises if expr1 is dimensionfull, as in meter^3. Here, things become difficult: in order to compute the type of the total expression expr1 ^ expr2, we need to know the value of expr2. For the meter^3 example, the answer is Length^3. This seems straightforward. However, the syntax of the language allows arbitrary expressions in the exponent. This is important to support use cases like the above e^(-x²/σ²). But it poses a problem for the type checker. In order to compute the type of expr1 ^ expr2, we need to fully evaluate expr2 at compile time. This is not going to work in general. Just think of a hypothetical expression like meter^f() where f() could do anything. Maybe even get some input from the user at runtime.

Numbat’s solution to this problem looks like this: If expr1 is not dimensionless, we restrict expr2 to a small subset of allowed operations that can be fully evaluated at compile time (similar to constexpr expressions in C++, const expressions in Rust, etc). Expressions like meter^(2 * (2 + 1) / 3) are completely fine and can be typechecked (Length^2), but things like function calls are not allowed and will lead to a compile time error.

To summarize: Given an exponentiation expression like expr1 ^ expr2, the type checker requires that:

• expr2 is of type Scalar
• One of the following:
• expr1 is also of type Scalar
• expr2 can be evaluated at compile time and yields a rational number.

#### Remark

We would probably need to enter the world of dependent types if we wanted to fully support exponentiation expressions without the limitations above. For example, consider the function f(x, n) = x^n. The return type of that function depends on the value of the parameter n.

To contact us, either open a GitHub issue or discussion, or pop into #numbat on Libera.Chat (link to webchat).