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.
\noexpandand\expandafterdo not store anything, they control expansion order and so are probably not directly related to your question. – David Carlisle Feb 15 '21 at 11:34