5

I was recently looking at this question about a primes macro, and i noticed that the solution that showed the one from the TeXbook was typesetting the prime numbers as the macro developed.

I than re created a macro very similar the the TeXbook solution to find the first [n] prime numbers:

\documentclass{article}
\usepackage{amsmath}

\newif\ifprime
\newcount\numOfPrimes \newcount\i \newcount\n \newcount\res \newcount\limit
\def\testdivision{%
    \res=\n \divide\res by\i \multiply\res by\i
    \ifnum\res=\n \global\primefalse \global\advance\i by\limit\fi}
\def\isprime{{% <-- group
    \i=3 \limit=\n \divide\limit by2 \global\primetrue
    \loop\ifnum\i<\limit \testdivision \advance\i by2 \repeat}}
\def\printprime{%
    \ifnum\numOfPrimes>1 , %
    \else\ifnum\numOfPrimes=1 ~and~%the last prime to print
    \fi\fi%
    \number\n \advance\numOfPrimes by-1}
\def\printifprime{%
    \isprime%test if n is prime
    \ifprime \printprime\fi}
\def\primes#1{%
    \ifnum#1=1 2\else\ifnum#1=2 2~and~3%
    \else 2,~3\numOfPrimes=#1 \advance\numOfPrimes by-2 \n=5%
        \loop\ifnum\numOfPrimes>0 \printifprime \advance\n by2 \repeat%
    \fi\fi}


\begin{document}
   The first 30 primes numbers are \primes{30}
\end{document}

Of course this solution works (and definitely not the fastest), but i was wondering how i can change the \printprime command that i wrote, so that it could 'store' all of the prime numbers, and then once the macro was finished it would typeset everything at once.

I saw that the other solutions here used \noexpand, \expandafter, and i don't really understand them, but i am pretty sure that those commands are used to store the primes without typesetting them.

If anyone can help me it would be very appreciated.

2
  • there are examples at this question tex.stackexchange.com/questions/134305/… (eg mine uses the Sieve of Eratosthenes so calculates them all at once not one at a time so there is no possibility of typesetting "as you go"). \noexpand and \expandafter do not store anything, they control expansion order and so are probably not directly related to your question. Feb 15, 2021 at 11:34
  • @DavidCarlisle my problem is that i don't really understand all of the \@tempcnt, \@ne... because im quite new to LaTeX macros... and i am pretty sure that the quality of coding in my macros show it. Do you have link or recommendation for explanations?
    – needle
    Feb 15, 2021 at 14:41

2 Answers 2

2
\documentclass{article}
\usepackage{amsmath}

\newif\ifprime
\newcount\numOfPrimes \newcount\i \newcount\n \newcount\res \newcount\limit
\def\testdivision{%
    \res=\n \divide\res by\i \multiply\res by\i
    \ifnum\res=\n \global\primefalse \global\advance\i by\limit\fi}
\def\isprime{{% <-- group
    \i=3 \limit=\n \divide\limit by2 \global\primetrue
    \loop\ifnum\i<\limit \testdivision \advance\i by2 \repeat}}
\def\storeprime#1{%
    \ifnum\numOfPrimes>1 \addtolist#1{, }%
    \else\ifnum\numOfPrimes=1 \addtolist#1{~and~}%the last prime to print
    \fi\fi
    \expandafter\addtolist\expandafter#1\expandafter{\number\n}\advance\numOfPrimes by-1}
\def\storeifprime#1{%
    \isprime%test if n is prime
    \ifprime \storeprime#1\fi}
\def\makelistofprimes#1#2{%
    \def#1{}% initialize
    \ifnum#2=1 \addtolist#1{2}\else\ifnum#2=2 \addtolist#1{2~and~3}%
    \else \def#1{2,~3}\numOfPrimes=#2 \advance\numOfPrimes by-2 \n=5
        \loop\ifnum\numOfPrimes>0 \storeifprime#1\advance\n by2 \repeat
    \fi\fi}
\def\addtolist#1#2{%
  \expandafter\def\expandafter#1\expandafter{#1#2}%
}

\begin{document}

\makelistofprimes\mylist{30}

\show\mylist % show the result on the terminal

The first 30 primes numbers are \mylist

\end{document}

Some of the macros in your code have been modified to accept as argument the name of the list to be populated. The key is \addtolist that, with a suitable chain of \expandafter tokens will cause expansion of the list to its previous value. A similar chain is needed for expanding \number when adding a found prime.

One could think to \def\addtolist#1#2{\edef#1{#1#2}}, but this would break the usage of ~.

Output on the terminal:

> \mylist=macro:
->2,~3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 
73, 79, 83, 89, 97, 101, 103, 107, 109~and~113.

An implementation with a more efficient algorithm based on the sieve of Eratosthenes:

\documentclass{article}

% Rosser, p_n < n(log n + 2 log log n) for n ≥ 2
% J. B. Rosser, The n-th prime is greater than n log n, 
% Proc. London Math. Soc., ser. 2, 45 (1939), 21–44

% Sieve of Eratosthenes algorithm taken from 
% https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes#Pseudocode

\ExplSyntaxOn

\NewDocumentCommand{\makelistofprimes}{smm}
 {% #1 = name of the list, #2 = number of primes to generate
  \seq_clear_new:c { l_eratosthenes_#2_seq }
  \int_compare:nTF { #3 = 1 }
   { \seq_put_right:cn { l_eratosthenes_#2_seq } { 2 } }
   { \__eratosthenes_list:nn { #2 } { #3 } }
  \IfBooleanT {#1} { \seq_use:cn { l_eratosthenes_#2_seq } {,~} }
 }

\int_new:N \l__eratosthenes_count_int
\int_new:N \l__eratosthenes_step_int

\cs_new_protected:Nn \__eratosthenes_list:nn
 {
  \intarray_new:cn { g__eratosthenes_#1_intarray }
   { \fp_eval:n { ceil(#2*(ln(#2)+2*ln(ln(#2)))) } }
  \int_set:Nn \l__eratosthenes_count_int { \intarray_count:c { g__eratosthenes_#1_intarray } }
  \intarray_gset:cnn { g__eratosthenes_#1_intarray } { 1 } { 1 } % 1 is not prime
  \int_step_inline:nnn { 2 } { \fp_eval:n { floor( sqrt(\l__eratosthenes_count_int) ) } }
   {
    \int_compare:nT { \intarray_item:cn { g__eratosthenes_#1_intarray } { ##1 } = 0 }
     {% the current number is prime
      \int_step_inline:nnnn { ##1*##1 } { ##1 } { \l__eratosthenes_count_int }
       { \intarray_gset:cnn { g__eratosthenes_#1_intarray } { ####1 } { 1 } }
     }
   }
  \int_zero:N \l__eratosthenes_step_int
  \int_while_do:nn { \seq_count:c { l_eratosthenes_#1_seq } < #2 }
   {
    \int_incr:N \l__eratosthenes_step_int
    \int_compare:nT { \intarray_item:cn { g__eratosthenes_#1_intarray } { \l__eratosthenes_step_int } = 0 }
     {
      \seq_put_right:cx { l_eratosthenes_#1_seq } { \int_eval:n { \l__eratosthenes_step_int } }
     }
   }
 }

\ExplSyntaxOff

\begin{document}

The first 100 prime numbers are \makelistofprimes*{x}{100}

The first 10 prime numbers are \makelistofprimes*{y}{10}

\end{document}

This saves the list in a sequence that can then be reused in several ways. For implementation reasons, a new name is required for every call (this might be changed).

enter image description here

The idea is to allocate an array of integers, but this requires to specify the number of items in it, so I use a bound on the n-th prime found in the cited article. The array is initially populated with zeros, so it’s simpler to use 1 as the mark for a nonprime.

Next the sieve is implemented as in the referenced Wikipedia page, with the only difference that 1 and 0 are switched. Finally a sequence is populated by traversing the array, picking up primes until we reach n of them.

1

Here an expandable but slow and inefficient approach based on recursive macros for performing trial division, one by one testing in ascending order each element of the set of odd natural numbers, dividing the odd natural number to test by those of the primes already found whose squares are not larger than the odd natural number to test.

Dividing by these numbers is sufficient because

  • when splitting a natural number in two natural factors, either both factors equal the square root of that number—this case occurs only for square numbers, or one factor is smaller than the square root of that number and the other factor is larger than the square root of that number.
  • the set of prime numbers already found in any case contains at least one element which is larger than the square root of the number to test. This can easily be deduced from the Bertrand–Chebyshev theorem:
    "For every n>1 there is always at least one prime p such that n<p<2n."
    Vice versa:
    For every even n>2 there is always at least one prime p such that n/2 < p < n.
    For every odd n>3 there is always at least one prime p such that (n-1)/2 < p < (n-1).
    Look at the cases for which sqrt(n) <= n/2 respective sqrt(n) <= (n-1)/2.

\numexpr from the ε-TeX-extensions is used for doing the arithmetic. This and the above-mentioned condition "whose squares are not larger than the odd natural number to test" restrict the range of numbers. You could use the expandable routines of the package bigintcalc instead, but this wouldn't make things faster.

As you asked for the entire list of primes to be delivered at once, during recursion the prime numbers found so far are accumulated within a macro-argument. Therefore another noteworthy restriction is the amount of tokens that can be stored as a macro-argument.

The routine \primes{⟨natural number k⟩} delivers the first ⟨k⟩ prime numbers as a list of undelimited/curly-brace-nested arguments.

The routine \PrintPrimes{⟨natural number k⟩} delivers the first ⟨k⟩ prime numbers as a comma-and space-separated list. (\PrintPrimes iterates on the elements of the list created via \primes, stripping off curly braces and prepending a comma and a space before each element but the first element of the list. \relax is used as "sentinel-token" marking the end of the list.)

Due to \romannumeral-expansion both routines deliver the result after triggering two expansion-steps on them/after having \primes/\PrintPrimes "hit" by \expandafter twice.

With both routines in case ⟨k⟩ is a non-positive integer in the range of non-positive integers which TeX can cope with, silently no token at all is delivered.

\documentclass{article}

\newcommand\UDExchange[2]{#2#1}%
\newcommand\UDfirstoftwo[2]{#1}%
\newcommand\UDsecondoftwo[2]{#2}%
\newcommand\UDfirstofone[1]{#1}%
\csname @ifdefinable\endcsname\UDstopromannumeral{\chardef\UDstopromannumeral=`\^^00}%

\newcommand\primes[1]{%
  \romannumeral
  \ifnum#1<1 \expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi
  {\UDstopromannumeral}{%
    \ifnum#1=1 \expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi
    {\UDstopromannumeral{2}}{%
      \expandafter\expandafter\expandafter\UDfirstofone
      \expandafter\primesloop\expandafter{\expandafter5\expandafter}%
      \expandafter{\number\numexpr#1-2\relax}{3}\relax{{3}}%
    }%
  }%
}%
\csname @ifdefinable\endcsname\primesloop{%
  % #1 - number to test
  % #2 - amount of primes still to find
  % #3 - current element of remaining list of primes found so far
  % #4 - remaining elements of remaining list of primes found so far
  % #5 - list of primes already found
  \long\def\primesloop#1#2#3#4\relax#5{%
    {\ifnum#2=0 \expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi
    {\UDstopromannumeral{2}#5}}{%
      \ifnum\number\numexpr#3*#3\relax>#1 \expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi%
      {% Hooray, a prime!
        \expandafter\expandafter\expandafter\UDfirstofone
        \expandafter\primesloop\expandafter{\number\numexpr#1+2\expandafter\relax\expandafter}%
        \expandafter{\number\numexpr#2-1\relax}#5{#1}\relax{#5{#1}}%
      }{%
         \ifnum\number\numexpr(#1/#3)*#3\relax=#1 \expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi
         {% Hooray, a composite!
           \expandafter\expandafter\expandafter\UDsecondoftwo
           \expandafter\primesloop\expandafter{\number\numexpr#1+2\relax}{#2}#5%
         }%
         {% Trial-division by the next prime already found.
           \expandafter\UDsecondoftwo\primesloop{#1}{#2}#4%
         }%
         \relax{#5}%
      }%
    }%
  }%
}%

\newcommand\PrintPrimes[1]{%
  \romannumeral
  \expandafter\expandafter\expandafter\UDExchange
  \expandafter\expandafter\expandafter{\primes{#1}}{\PrintPrimesLoop{}{}}\relax
}
\newcommand\PrintPrimesLoop[3]{%
  \ifx\relax#3\expandafter\UDfirstoftwo\else\expandafter\UDsecondoftwo\fi
  {\UDstopromannumeral#2}{\PrintPrimesLoop{, }{#2#1#3}}%
}%


\begin{document}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{1}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{2}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{3}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{4}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{10}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\primes{100}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{1}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{2}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{3}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{4}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{10}}%
\message{^^J\meaning\test^^J}

\expandafter\expandafter\expandafter\def
\expandafter\expandafter\expandafter\test
\expandafter\expandafter\expandafter{\PrintPrimes{100}}%
\message{^^J\meaning\test^^J}

\end{document}

When saving the example above as test.tex and compiling it, the console/the window of the shell where latex is called displays the following:

$ pdflatex test.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
(./test.tex
LaTeX2e <2020-10-01> patch level 4
L3 programming layer <2021-01-09> xparse <2020-03-03>
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/article.cls
Document Class: article 2020/04/10 v1.4m Standard LaTeX document class
(/usr/local/texlive/2020/texmf-dist/tex/latex/base/size10.clo))
(/usr/local/texlive/2020/texmf-dist/tex/latex/l3backend/l3backend-pdftex.def)
(./test.aux) 
macro:->{2}

macro:->{2}{3}

macro:->{2}{3}{5}

macro:->{2}{3}{5}{7}

macro:->{2}{3}{5}{7}{11}{13}{17}{19}{23}{29}


macro:->{2}{3}{5}{7}{11}{13}{17}{19}{23}{29}{31}{37}{41}{43}{47}{53}{59}{61}{67
}{71}{73}{79}{83}{89}{97}{101}{103}{107}{109}{113}{127}{131}{137}{139}{149}{151
}{157}{163}{167}{173}{179}{181}{191}{193}{197}{199}{211}{223}{227}{229}{233}{23
9}{241}{251}{257}{263}{269}{271}{277}{281}{283}{293}{307}{311}{313}{317}{331}{3
37}{347}{349}{353}{359}{367}{373}{379}{383}{389}{397}{401}{409}{419}{421}{431}{
433}{439}{443}{449}{457}{461}{463}{467}{479}{487}{491}{499}{503}{509}{521}{523}
{541}

macro:->2

macro:->2, 3

macro:->2, 3, 5

macro:->2, 3, 5, 7

macro:->2, 3, 5, 7, 11, 13, 17, 19, 23, 29


macro:->2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67,
 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151,
 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239
, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 33
7, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 4
33, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 
541
(./test.aux) )
No pages of output.
Transcript written on test.log.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .