I wonder if someone could explain to me why

the standard approximation $\pi = \directlua{tex.sprint(math.pi)}$

works all right, whereas

the standard approximation $\pi = \directlua{tex.sprint(string.format(' %.3f', math.pi))}$

throws an Undefined control sequence. \end{document}

It is probably something very basic, but I just don't get it. Thanks in advance


The % (percent) symbol is special to both (La)TeX and Lua, but in very different ways. In (La)TeX, it's the default comment character. In Lua, it's one of the "magic" characters in pattern matching operations. (Lua's other "magic" characters are ^$().[]*+-?.)

The \directlua directive is expandable, in the TeX-specific sense of the word. This means that TeX scans the argument of \directlua for any macros; if macros are found, TeX will try to expand them. In your second example, when (La)TeX scans

\directlua{tex.sprint(string.format(' %.3f', math.pi))}

it doesn't come across any LaTeX macros, but it does encounter the % character, which it interprets as a comment character. Hence, everything through the end of the input line is ignored, including the two closing parentheses and the single closing curly brace. That's why you're getting an error message.

How to keep these two, rather incompatible, uses of % from interfering with each other? I can think of four strategies.

Load the Lua code in such a way that it's not processed by LaTeX inappropriately.

  • Place the Lua code in an external file (generally with extension .lua, e.g., file.lua) and load the code into LuaTeX with a \directlua{dofile("file.lua")} instruction.

  • In your main tex file, load the luacode package, which provides environments called luacode and luacode*, and place your Lua code in either a luacode or a luacode* environment.

Instead of \directlua, use an instruction which can handle \% as a substitute for % while executing Lua code.

  • Load the luacode package, which provides the macro \luaexec (to be used instead of \directlua), and "escape" all instances of % with a backslash, i.e., rewrite them as \%. E.g.,

    \luaexec{tex.sprint(string.format('\%.3f', math.pi))}

Define a macro called, say, \percentchar which expands to % without this character being interpreted by TeX as a comment character. This allows continued use of \directlua. (Many thanks to Joseph Wright for providing this suggestion and to @egreg for providing a pointer to an even more parsimonious definition of \percentchar!)

  • The fourth method works by making the % character non-special to TeX's eyes. Place the following instruction -- \@percentchar is defined by the LaTeX "kernel"; it outputs a catcode-12 ("other") form of % -- in the preamble:

    %% or: \begingroup\catcode`\%=12\relax\gdef\percentchar{%}\endgroup 

    Then, execute this instruction in the body of the document:

    \directlua{tex.sprint(string.format('\percentchar.3f', math.pi))}

    Recall that \directlua is expandable, i.e., it'll expand \percentchar to %, which is the character Lua needs to see in the string.format function. And, because this form of % has been given catcode 12 ("other"), it won't be misinterpreted as a comment character by LaTeX -- and all is well. :-)

    While this fourth approach may seem a bit cumbersome at first, it enjoys the undeniable virtue of being implementable without having to load any packages (such as the luacode package) or having to place the Lua code in an external file.

Which of these four methods one should use depends crucially on what one needs to accomplish when making a Lua call. If it's just a single instance of a simple instruction, the third and fourth methods may be easiest. If the Lua code is long and complex, the first and second methods are almost certainly to be preferred.

The first three possibilities are illustrated in the following example.

enter image description here

%% create an external file, to store Lua code
function printpi (n) -- n: number of decimal digits to be shown
  tex.sprint ( string.format ( '%.'..n..'f' , math.pi ))

\directlua{ dofile ( "aux.lua" ) } % load Lua code from external file

\usepackage{luacode} % load 'luacode' package
function pi3() -- this function doesn't take an argument
  tex.sprint ( string.format ( '%.3f' , math.pi ) )

$\pi \approx \directlua{printpi(3)}$

$\pi \approx \directlua{pi3()}$

$\pi \approx \luaexec{tex.sprint(string.format('\%.3f', math.pi))}$
  • 2
    Perhaps add that you could simply define appropriate macros and use in \directlua, e.g. \begingroup\catcode`\%=12\relax\gdef\percentchar{%}\endgroup then \directlua{tex.sprint(string.format('\percentchar.3f', math.pi))} – Joseph Wright Jun 19 '18 at 8:32
  • 1
    @JosephWright - Many thanks for this suggestion. I've updated my answer to incorporate it. – Mico Jun 19 '18 at 9:01
  • 3
    @JosephWright Or, more simply, \makeatletter\let\percentchar\@percentchar\makeatother – egreg Jun 19 '18 at 9:12
  • @egreg - Excellent suggestion! :-) I'll update my answer accordingly. – Mico Jun 19 '18 at 9:12
  • 1
    @Mico, thank you ever so much to you and to egreg and JosephWright. Apparently, the use of the filename "aux.lua" in \begin{filecontents*}{aux.lua} gives rise to problems, at least with MIKTEK (lualatex.log): FATAL lualatex - No such file or directory: path="./aux.lua.tex" FATAL lualatex - Info: path="./aux.lua.tex" FATAL lualatex - Source: Libraries\MiKTeX\Core\File\win\winFile.cpp FATAL lualatex - Line: 561 INFO lualatex - finishing with exit code 1 if you use any other name, such "myfileLua.lua", everything is fine. Thanks again for your help!!! – A Navarro Jun 19 '18 at 17:05

Here is another idea. Simply round of math.pi and show the result. Lua does not have a function that rounds up to n decimal places, but it is relatively simply to create one. The code below is in ConTeXt, but should be straight forward to translate to LaTeX as well:

  local floor, ceil = math.floor, math.ceil

  math.Round = function(x, n)
      local factor = math.pow(10, n or 0)
      local y = x * factor
      if x >= 0 then 
          y = floor(y + 0.5) 
          y = ceil(y - 0.5)

      return y / factor

$π \approx \ctxlua{context(math.Round(math.pi, 3))}$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.