Skip to content

Instantly share code, notes, and snippets.

@VincentTam
Last active August 14, 2025 13:16
Show Gist options
  • Save VincentTam/f409f74ac120e4e7163699a351572d98 to your computer and use it in GitHub Desktop.
Save VincentTam/f409f74ac120e4e7163699a351572d98 to your computer and use it in GitHub Desktop.
approximations for exponential function using two different constructions
\newcommand*\mytablecontents{}
\gappto\mytablecontents{$n$ & $N=10^n$ & $(1+1/N)^N$}
\gappto\mytablecontents{\\\hline}
\foreach \i in {1,...,5}{
\FPeval\ii{pow(\i,10)}
\FPeval\ii{round(ii:0)}
\FPeval\ieval{round((1+1/ii)^ii:8)}
\xappto\mytablecontents{\i & $\ii$ & $\ieval$}
\gappto\mytablecontents{\\\hline}
}
$\begin{NiceTabular}{|*{3}{r|}}
\hline
\mytablecontents
\end{NiceTabular}$
#let count = 5 // number of observations
#let nums = range(1, count + 1)
#let tenPN = n => calc.pow(10, n)
#let aTenPN = n => calc.pow(1 + 1/tenPN(n), tenPN(n))
#align(center, table(
columns: 3,
align: right,
table.header($n$, $N = 10^n$, $(1 + 1\/N)^N$),
..nums.map(n =>
(
str(n),
str(calc.round(tenPN(n), digits: 10)),
str(calc.round(aTenPN(n), digits: 10))
)
).flatten(),
))
% approximate Euler's number using series
\newcommand\myR{10} % r: argument for exponential function
\newcommand\numdp{10} % precision: #dp
\newcommand\numiter{100} % number of iterations
\newcommand\numrows{25} % number of rows in table
\newcommand\myepsilon{0.0000000001} % machine epsilon
% https://tex.stackexchange.com/a/133439/126386
\ExplSyntaxOn
\NewExpandableDocumentCommand{\fpcompare}{ m m m }
{
% #1 = test to perform
% #2 = text for the true case
% #3 = text for the false case
\fp_compare:nTF { #1 } { #2 } { #3 }
}
\ExplSyntaxOff
\newcounter{myrows}
\newcommand*\mytablecontents{}
\gappto\mytablecontents{$n$ & $a_n = r^n/n!$ & $S_n = \textstyle\sum_{k=0}^n a_k$}
\gappto\mytablecontents{\\\hline}
\foreach \i [
remember=\Sn as \lastSn (initially 1),
remember=\curFrac as \lastFrac (initially 1),
] in {1,...,\numiter}{
\FPeval\curFrac{lastFrac*myR/i}
\FPeval\an{curFrac}
\FPeval\Sn{lastSn+an}
\FPeval\anPr{round(an:numdp)}
\FPeval\SnPr{round(Sn:numdp)}
% display some output
\ifnum \i < 21
\xappto\mytablecontents{\i & $\anPr$ & $\SnPr$}
\gappto\mytablecontents{\\\hline}
\stepcounter{myrows}
\else
\fpcompare{abs(\an) < \myepsilon && \value{myrows} < \numrows}
{
\xappto\mytablecontents{\i & $\anPr$ & $\SnPr$}
\gappto\mytablecontents{\\\hline}
\stepcounter{myrows}
}{}
\fi
}
$\begin{NiceTabular}{|*{3}{r|}}
\hline
\mytablecontents
\end{NiceTabular}$
#let numrows = 25 // total number of displayed rows
#let rowCnt = 0 // number of rows will be displayed
#let r = -16 // input argument for exponential function
#let myepsilon = 0.00000000001 // machine epsilon
#let precision = 10 // number of d.p.
#let n = 0
#let an = 1
#let sn = 1
#let rows = ((n,an,sn),)
#while rowCnt < numrows {
n += 1
an *= r / n
sn += an
if n < 21 or calc.abs(an) < myepsilon {
rows.push((n, calc.round(an, digits: precision), calc.round(sn, digits: precision)))
rowCnt += 1
}
}
#let _ = rows.remove(0)
#align(center, table(
columns: 3,
align: right,
table.header($n$, $a_n = r^n \/ n!$, $s_n = sum_(k=1)^n a_k$),
..rows.flatten().map(str),
))

Comparison of two different definitions for exponential function

Binomial definition

$n$ $N = 10^n$ $(1 + 1/N)^N$
1 10 2.59374246
2 100 2.70481383
3 1000 2.71692393
4 10000 2.71814593
5 100000 2.71826824

Very slow. After 10⁵ iterations, it's correct only up to 4d.p.

Series definition

$n$ $a_n = 1/n!$ $S_n = \sum_{k=0}^{n} a_k$
1 1.0000000000 2.0000000000
2 0.5000000000 2.5000000000
3 0.1666666667 2.6666666667
4 0.0416666667 2.7083333333
5 0.0083333333 2.7166666667
6 0.0013888889 2.7180555556
7 0.0001984127 2.7182539683
8 0.0000248016 2.7182787698
9 0.0000027557 2.7182815256
10 0.0000002756 2.7182818011
11 0.0000000251 2.7182818262
12 0.0000000021 2.7182818283
13 0.0000000002 2.7182818284
14 0.0000000000 2.7182818285
15 0.0000000000 2.7182818285
16 0.0000000000 2.7182818285
17 0.0000000000 2.7182818285
18 0.0000000000 2.7182818285
19 0.0000000000 2.7182818285
20 0.0000000000 2.7182818285

Slightly improved code for generalization to exp(r)

A slight modification to curFrac (by adding *myR to lastFrac/i) will give a basic numerical scheme for the exponential function.

In my original code, in the intrinsic filter, I've forgotten to use absolute value in abs(an) < myepsilon.

It's not interesting too see so many rows of zero, so I've changed it to \ifnum \i < 21. Note that neither <= nor break won't work. A more efficient way would be to use a while loop on counter myrows, but there're better tech for this, so I'm not spending time on this.

I tried LaTeX's calculations at first, but it's too inaccurate. I've to use fpu package for more accurate calculations.

rewritten in typst

Just a recollection of what I've said on Discord.

LaTeX vs typst

The most noticeable advantage of typst over LaTeX is that you don't have to type that much \, say beta for β instead of \beta The second would be more intuitive syntax. for some symbols, their typst syntax is nearer to English than LaTeX, like A union B for AB instead of A cup B LaTeX's programming isn't quite easy. the expansion rules can be quite hard to understand. typst was developed in Rust, and programmers should be no problem writing typst functions to make code reusable. If you care about compilation speed (say you've made a 100-page document or more), then typst's advantage would surface up

So what're LaTeX advantages? Some "soft" answers include popularity, worldwide acceptance (say in all big math online communities, even on GitHub & GitLab, LaTeX rendering is supported). Its long history indicates a great community support (on TeX.SE, etc) and pool of packages. It can handle complex situations. (↑ disturbances, ↑ complexity)

I'm sure that users will have their own strategy to make use of these technologies and it'll be great to have them available

Here's a simple example of LaTeX programming

To do recursion in LaTeX, we often need to think about text expansion, cuz this langauge is about text substitution. I have to rely on ChatGPT to understand how \expandafter, \xappto, \gappto work. The final code would have bunch of syntax that has nothing to do with the core logic.

\newcommand\unions[2]{
  \ifnum#1>1 \expandafter\unions\expandafter{\number\numexpr#1-1\relax}{#2} \cup {#2}_{#1}
  \else
    {#2}_1
  \fi
}
$\unions{6}{S}$

If you're interested in typst, here's a demo from Evan Chen. Hope that would motivate you to a typst tutorial.

Looking back at what I've written, I found that I've overlooked \tikzmath's function However, while writing in LaTeX, I found it difficult to separate "logic" & "display" LaTeX does have arrays, but it's less operable than those in typst I don't wanna spam this chat, so I'll be posting examples below.

typst examples analysis

See how i separated

  • input
  • logic (to construct the table array)
  • display into three parts in my code

rows contains the table rows for display.
After each iteration, I rows.push((n, an, sn)) ← ofc i rounded those cells
The display part is a bit tricky, but once you've figured out the logic, it's easy to write.
Our target is to feed typst's table() function with cell contents separated with comma ,, like row1 cell 1, row 1 cell 2, row 2 cell 1, row 2 cell 2, …

Any developer will have no problem understanding these steps:

  1. rows is an array of array, sth like ((a, b, c), (d, e, f), …)
  2. rows.flatten() return another array without the inner (): (a, b, c, d, e, f, …)
  3. this is followed by .map(str), so that the function str is applied to each element of the above array in step 2, i.e. (str(a), str(b), str(c), …)
  4. the above array is prepended by the spreading operator .., which takes away the () wrapping the input (e.g. ..(1, 2)1, 2), so that it's ready for table()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment