Advanced scientific number formatting for Typst.
- Introduction
- Quick Demo
- Number Formatting
- Table Alignment
- Units and Quantities
- Zero for Third-Party Packages
- Changelog
Introduction
Proper number formatting is essential for clear and readable scientific documents. Zero provides tools for consistent formatting and simplifies adherence to established publication standards. Key features include:
- Standardized formatting
- Digit grouping, e.g., 299 792 458 instead of 299 792 458
- Plug-and-play number alignment in tables
- Quick scientific notation, e.g.,
"2e4"
becomes 2×10⁴ - Symmetric and asymmetric uncertainties
- Rounding in various modes
- Unit and quantity formatting
- Helpers for package authors
A number in scientific notation consists of three parts: the mantissa, an optional uncertainty, and an optional power (exponent). The following figure illustrates the anatomy of a formatted number:
Quick Demo
Number Formatting
num
Zero’s core is the num()
function, which provides flexible number formatting. Its defaults can be configured via set-num()
.
#num(
number: str | content | int | float | dictionary | array,
digits: auto | int = auto,
fixed: none | int = none,
decimal-separator: str = ".",
product: content = sym.times,
tight: bool = false,
math: bool = true,
omit-unity-mantissa: bool = true,
positive-sign: bool = false,
positive-sign-exponent: bool = false,
base: int | content = 10,
uncertainty-mode: str = "separate",
round: dictionary,
group: dictionary,
)
-
number: str | content | int | float | array
: Number input;str
is preferred. If the input iscontent
, it may only contain text nodes. Numeric typesint
andfloat
are supported but not encouraged because of information loss (e.g., the number of trailing “0” digits or the exponent). The remaining typesdictionary
andarray
are intended for advanced use, see below. -
digits: auto | int = auto
: Truncates the number at a given (positive) number of decimal places or pads the number with zeros if necessary. This is independent of rounding. -
fixed: none | int = none
: If notnone
, forces a fixed exponent. Additional exponents given in the number input are taken into account. -
decimal-separator: str = "."
: Specifies the marker that is used for separating integer and decimal part. -
product: content = sym.times
: Specifies the multiplication symbol used for scientific notation. -
tight: bool = false
: If true, tight spacing is applied between operands (applies to × and ±). -
math: bool = true
: If set tofalse
, the parts of the number won’t be wrapped in amath.equation
. This makes it possible to usenum()
with non-math fonts. -
omit-unity-mantissa: bool = false
: Determines whether a mantissa of 1 is omitted in scientific notation, e.g., 10⁴ instead of 1·10⁴. -
positive-sign: bool = false
: If set totrue
, positive coefficients are shown with a + sign. -
positive-sign-exponent: bool = false
: If set totrue
, positive exponents are shown with a + sign. -
base: int | content = 10
: The base used for scientific power notation. -
uncertainty-mode: str = "separate"
: Selects one of the modes"separate"
,"compact"
, or"compact-separator"
for displaying uncertainties. The different behaviors are shown below: -
round: dictionary
: You can provide one or more rounding options in a dictionary. Also see rounding. -
group: dictionary
: You can provide one or more grouping options in a dictionary. Also see grouping.
Configuration example:
#set-num(product: math.dot, tight: true)
Grouping
Digit grouping is important for keeping large figures readable. It is customary to separate thousands with a thin space, a period, comma, or an apostrophe (however, we discourage using a period or a comma to avoid confusion since both are used for decimal separators in various countries).
Digit grouping can be configured with the set-group()
function.
#set-group(
size: int = 3,
separator: content = sym.space.thin,
threshold: int | dictionary = 5
)
size: int = 3
: Determines the size of the groups.separator: content = sym.space.thin
: Separator between groups.threshold: int | dictionary = 5
: Necessary number of digits needed for digit grouping to kick in. Four-digit numbers for example are usually not grouped at all since they can still be read easily. This parameter also accepts dictionary arguments of the form(integer: int, fractional: int)
to allow turning on grouping for only the integer or fractional part, for example(integer: 5, fractional: calc.inf)
.
Configuration example:
#set-group(separator: "'", threshold: 4)
Set threshold: calc.inf
to disable grouping.
Rounding
Rounding can be configured with the set-round()
function.
#set-round(
mode: none | str = none,
precision: int = 2,
pad: bool = true,
direction: str = "nearest",
)
mode: none | str = none
: Sets the rounding mode. The possible options arenone
: Rounding is turned off."places"
: The number is rounded to the number of decimal places given by theprecision
parameter."figures"
: The number is rounded to a number of significant figures given by theprecision
parameter."uncertainty"
: Requires giving an uncertainty value. The uncertainty is rounded to significant figures according to theprecision
argument and then the number is rounded to the same number of decimal places as the uncertainty.
precision: int = 2
: The precision to round to. Also see parametermode
.pad: bool = true
: Whether to pad the number with zeros if the number has fewer digits than the rounding precision.direction: str = "nearest"
: Sets the rounding direction."nearest"
: Rounding takes place in the usual fashion, rounding to the nearer number, e.g., 2.34 → 2.3 and 2.36 → 2.4."down"
: Always rounds down, e.g., 2.38 → 2.3 and 2.30 → 2.3."up"
: Always rounds up, e.g., 2.32 → 2.4 and 2.30 → 2.3.
Specifying Uncertainties
There are two ways of specifying uncertainties:
- Applying an uncertainty to the least significant digits using parentheses, e.g.,
2.3(4)
, - Denoting an absolute uncertainty, e.g.,
2.3+-0.4
becomes 2.3±0.4.
Zero supports both and can convert between these two, so that you can pick the displayed style (configured via uncertainty-mode
, see above) independently of the input style.
How do uncertainties interplay with exponents? The uncertainty needs to come first, and the exponent applies to both the mantissa and the uncertainty, e.g., num("1.23+-.04e2")
becomes
(1.23 ± 0.03)×10²
Note that the mantissa is now put in parentheses to disambiguate the application of the power.
In some cases, the uncertainty is asymmetric which can be expressed via num("1.23+0.02-0.01")
$$ 1.23^{+0.02}_{-0.01}. $$
Table Alignment
In scientific publication, presenting many numbers in a readable fashion can be a difficult discipline. A good starting point is to align numbers in a table at the decimal separator. With Zero, this can be easily accomplished by simply applying a show-rule to table
.
#{
show table: zero.format-table(none, auto, auto)
table(
columns: 3,
align: center,
$n$, $α$, $β$,
[1], [3.45], [-11.1],
..
)
}
Arguments to format-table
are none
, auto
, or dictionaries (see below) that turn on or off number alignment for individual columns. In the example above, we activate number alignment for the second and third column.
Be careful to scope the show-rule with curly {}
or square []
brackets to avoid applying the rule twice when changing format for the next table. You can use this in your figures in the following way:
#figure(
{
show table: zero.format-table(..)
table(..)
},
caption: []
)
Through this show-rule, Zero can interoperate seamlessly with many other table packages for Typst.
Nevertheless, Zero also provides the function ztable
that you can use as a drop-in replacement for the standard table function. It features an additional parameter format
which takes an array of none
, auto
, or dictionary
values to turn on number alignment for specific columns. With ztable
the above example can be recreated like this:
#ztable(
columns: 3,
align: center,
format: (none, auto, auto),
$n$, $α$, $β$,
[1], [3.45], [-11.1],
..
)
Protect Non-Numerical Content
Non-number entries (e.g., in the header) are automatically recognized in some cases and will not be aligned. In ambiguous cases, adding a leading or trailing space tells Zero not to apply alignment to this cell, e.g., [Angle ]
instead of [Angle]
.
In addition, you can prefix or suffix a numeral with content wrapped by the function nonum[]
to mark it as not belonging to the number. The remaining content may still be recognized as a number and formatted/aligned accordingly.
#ztable(
format: (auto,),
[#nonum[€]123.0#nonum(footnote[A special number])],
[12.111],
)
Advanced Table Options
Zero not only aligns numbers at the decimal point but also at the uncertainty and exponent part. Moreover, by passing a dictionary
instead of auto
, a set of num()
arguments to apply to all numbers in a column can be specified.
#ztable(
columns: 4,
align: center,
format: (none, auto, auto, (digits: 1)),
$n$, $α$, $β$, $γ$,
[1], [3.45e2], [-11.1+-3], [0],
..
)
Units and Quantities
Numbers are frequently displayed together with a (physical) unit forming a so-called quantity. Zero has built-in support for formatting quantities through the zi
module.
Zero takes a different approach to units than other packages: In order to avoid repetition (DRY principle) and to avoid accidental errors, every unit is
- first declared (or already predefined)
- and then used as a function to produce a quantity.
Take a look at the example below:
#import "@preview/zero:0.4.0": zi
#let kgm-s2 = zi.declare("kg m/s^2")
- The current world record for the 100 metres is held by Usain Bolt with #zi.s[9.58].
- The velocity of light is #zi.m-s[299792458].
- A Newton is defined as #kgm-s2[1].
- The unit of a frequency is #zi.Hz().
Declaring a New Unit
All common single units as well as a few frequent combinations have been predefined in the zi
module.
You can create a new unit through the zi.declare
function. We recommend the following naming convention to uniquely assign a variable name to the unit.
Configuring Units
The appearance of units can be configured via set-unit
:
#set-unit(
unit-separator: content = sym.space.thin,
fraction: str = "power",
breakable: bool = false
)
unit-separator: content
: Configures the separator between consecutive unit parts in a composite unit.fraction: str
: Configures the appearance of fractions when there are units present in the denominator. Possible options are"power"
: Units with negative exponents are shown as powers."fraction"
: When units with negative exponents are present, a fraction is created and the concerned units are put in the denominator."inline"
: An inline fraction is created.
breakable: bool
: Whether units and quantities can be broken across paragraph lines.
These options are also available when instancing a quantity, e.g., #zi.m(fraction: "inline")[2.5]
.
Note that the configuration made through set-num
also affects the numeral of a quantity.
Zero for Third-party Packages
This package provides some useful extras for third-party packages that generate formatted numbers (for example graphics libraries).
Instead of passing a str
to num()
, it is also possible to pass a dictionary of the form
(
mantissa: str | int | float,
e: none | str,
pm: none | array
)
This way, parsing the number can be avoided which makes especially sense for packages that generate numbers (e.g., tick labels for a diagram axis) with independent mantissa and exponent.
Furthermore, num()
also allows array
arguments for number
which allows for more efficient batch-processing of numbers with the same setup. In this case, the caller of the function needs to provide context
.
Lastly, the function align-columns
can be used to format and align an array of numerals into a single column. The returned array of items can be used to fill a column of a table
or stack
. Also here, the caller of the function needs to provide context
.
Changelog
Version 0.4.0
Units and quantities
- Adds the
zi
module for unit and quantity formatting. - Adds new way of applying table alignment via show-rules for seamless interoperability with other table packages.
- Adds option to configure the group threshold individually for the integer and fractional part.
- Fixes numbers in RTL direction context.
- Fixes
figure.kind
detection ofztable
. - Fixes direct usage of
nonum
inztable
. - Fixes uncertainties in combination with a
fixed
exponent.
Version 0.3.3
Fix
- Fixes an issue with negative numbers in parentheses due to a change in Typst 0.13.
Version 0.3.2
Fixes and more helpers for third-party package developers
- Adds
align-columns
for package developers. - Fixes issues arising for Typst 0.13.
Version 0.3.1
Improvements for tables and math-less mode
- Fixes
show
rules withtable.cell
for number-aligned cells. - Improves
math: false
mode: Formatting can now be handled entirely without equations which makes it possible to use Zero with fonts without math support. - Improves number recognition in tables. A number now needs to start with one of
0123456789+-,.
. This gets rid of many false positives (mostly encountered in header cells).
Version 0.3.0
Support for non-numerical content in number cells
- Adds
nonum[]
function that can be used to mark content in cells as not belonging to the number. The remaining content may still be recognized as a number and formatted/aligned accordingly. The content wrapped bynonum[]
is preserved. - Fixes number alignment tables with new version Typst 0.12.
Version 0.2.0
Performance and math-less mode
- Adds support for using non-math fonts for
num
via the optionmath
. This can be activated by calling#set-num(math: false)
. - Performance improvements for both
num()
andztable(9)
Version 0.1.0
Initial release