# Formatting numbers in Lua

Posted on September 9, 2019

I often use Lua to generate solution for homework assignments. Ideally, I want the solution to look exactly how it would look if it were written by hand. But this can be tricker than it appears at first glance. In this post, I’ll explain the issue and how I solve it.

To illustrate the formatting issue, let me consider an example of writing the solution of how to find roots of a quadratic equation. Let’s start with a simple example. First let’s define

\defineenumeration[question]
\defineenumeration[solution]


Then consider the following example.

\startquestion
Find the roots of $x^2 + 5x + 6 = 0$.
\stopquestion

\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = 1
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= -2 \text{ and } -3.
\stopformula
\stopsolution


Now suppose I want to generate a homework assignment with four or five such questions. In order to ensure that I don’t make any mistakes, I generate the questions and the answers using Lua. For simplicity, let’s assume that both roots are real. Then, I can use string.formatters to easily generate the assignment and the solution.

\startluacode

local formatters = string.formatters

local question = formatters[ [[
\startquestion
Find the roots of $%s x^2 + %s x + %s = 0$.
\stopquestion
]] ]

local solution = formatters[ [[
\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = %s.
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= %s \text{ and } %s.
\stopformula
\stopsolution
]] ]

local sqrt = math.sqrt

assignment = assignment or { }

assignment.roots = function(a, b, c)
context(question(a == 1 and "" or a, b, c))

D = b^2 - 4*a*c
r1 = (-b + sqrt(D))/(2*a)
r2 = (-b - sqrt(D))/(2*a)

context(solution(D, r1, r2))

end
\stopluacode


Then, in the homework assignment, I can generate the above question and its solution using:

\ctxlua{assignment.roots(1, 5, 6)}


The above generated solution works well when all numbers are integer valued. However, the generated solution is not ideal when some of the calculations result in floats. For example:

\ctxlua{assignment.roots(1, 4, 2)}


will generate:

\startquestion
Find the roots of $x^2 + 4x + 2 = 0$.
\stopquestion

\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = 8.0.
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= -0.5857864376269 \text{ and } -3.4142135623731.
\stopformula
\stopsolution


Technically, the solution is correct. But one never types a float with a precision of 12 decimal places. Of course, I could change the %s in the template to %.3f as follows:

local solution = formatters[ [[
\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = %.3f.
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= %.3f \text{ and } %.3f.
\stopformula
\stopsolution
]] ]


For the second problem, this will generate:

\startquestion
Find the roots of $x^2 + 4x + 2 = 0$.
\stopquestion

\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = 8.000.
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= -0.586 \text{ and } -3.414.
\stopformula
\stopsolution


which is partially acceptable (when typesetting the solution by hand, one would uses Δ = 8 rather than Δ = 8.000) but for the first problem, we now get

\startquestion
Find the roots of $x^2 + 5x + 6 = 0$.
\stopquestion

\startsolution
Let's start by computing the determinant.
\startformula
Δ = b^2 - 4ac = 1.000.
\stopformula
Since $Δ > 0$, the roots are given by
\startformula
r_{1,2} = \dfrac{ -b \pm \sqrt{Δ} }{ 2a }
= -2.000 \text{ and } -3.000.
\stopformula
\stopsolution


which is not ideal.

So, to typeset such examples, I need to format numbers as follows:

• If the number is an integer, use %d.
• If the number is a float, use %.3f.

However, once you think about it, the above spec is not complete. In addition, what we want is that

• If %.3f gives "0.000", using (the tex equivalent of) %.3e.

Who knew simply formatting numbers could be so complicated! Anyways, here is a simple function that does this formatting:

local mathtype, floor  = math.type, math.floor
local format, strlen, match = string.format, string.len, string.match

formatnumber = function(a)
if mathtype(a) == "integer" or floor(a) == a then
return format("%d", a)
else
str = format("%s", a)
fmt = format("%.3f", a)
exp = format("%.3e", a)
if strlen(str) < strlen(fmt) then
return str
elseif fmt == "0.000" then
x = match(exp, "(.*)e")
y = match(exp, "e(.*)")
return format("%s \\times 10^{%d}", x, y)
else
return fmt
end
end
end


We first check if the number is an integer (using math.type(a) == "integer") or if the number is a float of the type 8.0 (using math.floor(a) == a) and if so, format the number using %d.

We then next check if casting the number to a string leads to a shorter string than formatting it using %.3f. If so, we format it using %s. This ensures that a number like 8.2 is typeset as 8.2 rather than 8.200.

Finally, we check if formatting the number using %.3f gives "0.000". If so, we typeset the tex equvalent of %.3e.

Phew!

To use this code, I use %s in my templates, and then call the template as

context(solution(formatnumber(D), formatnumber(r1), formatnumber(r2)))


This gives me correct formatting in all the edge cases.

This entry was posted in Formatting and tagged luatex, programming.