1167 lines
32 KiB
HTML
1167 lines
32 KiB
HTML
<!doctype html>
|
||
<html lang="en-US">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<link rel="stylesheet" href="/styles/default.css" />
|
||
<link rel="stylesheet" href="/styles/junk.css" />
|
||
<link rel="stylesheet" href="/styles/sieves.css" />
|
||
<link rel="icon" href="/images/favicon.ico" />
|
||
<title>Sieving Multiplicative Functions</title>
|
||
</head>
|
||
|
||
<body>
|
||
<header>
|
||
<a id="homelink" href="/">dairydemon.net</a>
|
||
</header>
|
||
|
||
<main>
|
||
<article>
|
||
<hgroup>
|
||
<h1>Sieving Multiplicative Functions</h1>
|
||
<p>Posted <time datetime="2026-04-15">April 15, 2026</time></p>
|
||
</hgroup>
|
||
|
||
<p>The
|
||
<a href="https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes">sieve of Eratosthenes</a>
|
||
is a well-known algorithm for computing all the prime numbers up to a
|
||
given bound. There are also well-known variants on the sieve. In
|
||
particular, a segmented version can be implemented to limit memory
|
||
usage.</p>
|
||
|
||
<p>Furthermore, a similar approach can be used to "sieve" the values of
|
||
<a href="https://en.wikipedia.org/wiki/Multiplicative_function">multiplicative functions</a>.
|
||
For instance,
|
||
<a href="https://cp-algorithms.com/algebra/phi-function.html">this page</a>
|
||
discusses computing values of
|
||
<a href="https://en.wikipedia.org/wiki/Euler%27s_totient_function">Euler's totient function</a>
|
||
with an approach that results in the same time complexity as the
|
||
standard sieve.</p>
|
||
|
||
<p>
|
||
<a href="https://codeforces.com/blog/entry/54090">This Codeforces page</a>
|
||
also discusses an approach for sieving <em>any</em> multiplicative
|
||
function. However, the provided code has some downsides: it's not
|
||
segmented, and the modifications needed for a given multiplicative
|
||
function are not always obvious.</p>
|
||
|
||
<p>Here, I'll discuss a variant of the sieve that
|
||
<ul>
|
||
<li>is segmented,</li>
|
||
<li>works for any multiplicative function,</li>
|
||
<li>works with a multiplicative function's "prime power" definition,
|
||
and</li>
|
||
<li>has the same time complexity as the standard sieve (assuming the
|
||
function has an efficient implementation for prime powers).</li>
|
||
</ul>
|
||
</p>
|
||
|
||
<section>
|
||
<h2>Demo</h2>
|
||
<p>Note that for large ranges, outputting the full result will chew
|
||
up a lot of memory.</p>
|
||
<div id="demo-inputs">
|
||
<label id="lower-label" for="lower"><math><mi>l</mi></math> (lower bound)</label>
|
||
<input inputmode="numeric" pattern="\d+" id="lower" />
|
||
|
||
<label id="upper-label" for="upper"><math><mi>r</mi></math> (upper bound)</label>
|
||
<input inputmode="numeric" pattern="\d+" id="upper" />
|
||
|
||
<label id="function-label" for="function">Function</label>
|
||
<select id="function">
|
||
<option value="totient">Totient</option>
|
||
<option value="num-divisors">Number of divisors</option>
|
||
<option value="sum-divisors">Sum of divisors</option>
|
||
<option value="radical">Radical</option>
|
||
<option value="mobius">Mobius</option>
|
||
<option value="big-omega">Prime omega (with multiplicity)</option>
|
||
<option value="little-omega">Prime omega (without multiplicity)</option>
|
||
</select>
|
||
|
||
<input type="button" id="enter" value="Enter" />
|
||
</div>
|
||
|
||
<output id="result" for="limit function"></output>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Multiplicative functions</h2>
|
||
<p>
|
||
<a href="https://en.wikipedia.org/wiki/Multiplicative_function">Multiplicative functions</a>
|
||
are functions with a special property: if two numbers
|
||
<math><mi>a</mi></math> and <math><mi>b</mi></math> are
|
||
<a href="https://en.wikipedia.org/wiki/Coprime_integers">coprime</a>
|
||
(i.e. their
|
||
<a href="https://en.wikipedia.org/wiki/Greatest_common_divisor">greatest common divisor</a>
|
||
is 1), then
|
||
<math>
|
||
<mrow>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>a</mi>
|
||
<mi>b</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>a</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>b</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
</mrow>
|
||
</math>.
|
||
This means if we can factor a number <math><mi>n</mi></math> into
|
||
coprime integers <math><mi>x</mi></math> and <math><mi>y</mi></math>,
|
||
we can find <math><mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo></math> by
|
||
instead finding <math><mi>f</mi><mo>(</mo><mi>x</mi><mo>)</mo></math>
|
||
and <math><mi>f</mi><mo>(</mo><mi>y</mi><mo>)</mo></math> and
|
||
multiplying them.</p>
|
||
|
||
<p>Going a step further, if we have a prime factorization of a number
|
||
<math><mi>n</mi></math>, along with a formula for
|
||
<math><mi>f</mi></math> at prime powers, we can easily compute
|
||
<math><mi>f</mi><mo>(</mo><mi>n</mi><mo>)</mo></math> by evaluating
|
||
<math><mi>f</mi></math> at each prime power and multiplying.</p>
|
||
|
||
<p>It turns out there are lots of multiplicative functions, including
|
||
many important functions in number theory, such as:</p>
|
||
<ul>
|
||
<li><a href="https://en.wikipedia.org/wiki/Divisor_function">the divisor function</a></li>
|
||
<li><a href="https://en.wikipedia.org/wiki/Euler%27s_totient_function">Euler's totient function</a></li>
|
||
<li><a href="https://en.wikipedia.org/wiki/M%C3%B6bius_function">the Mobius function</a></li>
|
||
</ul>
|
||
<p>Furthermore, many of these functions have very simple formulas for
|
||
prime power inputs, making them easy to evaluate if you have a prime
|
||
factorization.</p>
|
||
|
||
<section>
|
||
<h3>An example</h3>
|
||
<p>Suppose we want to know how many divisors 120 has. We
|
||
<em>could</em> try counting every divisor by hand, but that would
|
||
be pretty tedious and error-prone. We could also write a simple
|
||
loop to have a computer find the answer, but that approach wouldn't
|
||
scale well for larger numbers. Instead, we'll take advantage of the
|
||
fact that the
|
||
<a href="https://en.wikipedia.org/wiki/Divisor_function">number of divisors function</a>
|
||
is multiplicative to find the answer.</p>
|
||
|
||
<p>We'll use <math><mi>d</mi><mo>(</mo><mi>n</mi><mo>)</mo></math>
|
||
to denote the function. It has a convenient formula for prime
|
||
powers:<sup>1</sup></p>
|
||
|
||
<div class="math-block">
|
||
<math display="block" class="tml-display" style="display:block math;">
|
||
<mrow>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<msup>
|
||
<mi>p</mi>
|
||
<mi class="tml-med-pad">k</mi>
|
||
</msup>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>k</mi>
|
||
<mo>+</mo>
|
||
<mn>1</mn>
|
||
</mrow>
|
||
</math>
|
||
</div>
|
||
|
||
<p>This lets us answer our question by factoring
|
||
120:<sup>2</sup></p>
|
||
|
||
<div class="math-block">
|
||
<math display="block" class="tml-display" style="display:block math;">
|
||
<mrow>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mn>120</mn>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<msup>
|
||
<mn>2</mn>
|
||
<mn>3</mn>
|
||
</msup>
|
||
<mo>×</mo>
|
||
<mn>3</mn>
|
||
<mo>×</mo>
|
||
<mn>5</mn>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<msup>
|
||
<mn>2</mn>
|
||
<mn>3</mn>
|
||
</msup>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mn>3</mn>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mi>d</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mn>5</mn>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mn>4</mn>
|
||
<mo>×</mo>
|
||
<mn>2</mn>
|
||
<mo>×</mo>
|
||
<mn>2</mn>
|
||
<mo>=</mo>
|
||
<mn>16</mn>
|
||
</mrow>
|
||
</math>
|
||
</div>
|
||
|
||
<p>Sure enough, the 16 divisors are 1, 2, 3, 4, 5, 6, 8, 10, 12,
|
||
15, 20, 24, 30, 40, 60, and 120.</p>
|
||
|
||
<p>Suppose, however, that we want to find the number of divisors
|
||
for <em>each number</em> up to 120.
|
||
<a href="https://en.wikipedia.org/wiki/Integer_factorization">There's no general algorithm to efficiently factor a number</a>,
|
||
so it isn't a great idea to factor every number to get the
|
||
solution. We'll take a different approach, but it will still take
|
||
advantage of the multiplicative property.</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h3>What about additive functions?</h3>
|
||
<p>There's another class of functions called
|
||
<a href="https://en.wikipedia.org/wiki/Additive_function">additive functions</a>
|
||
with similar properties to multiplicative functions, namely that
|
||
<math>
|
||
<mrow>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>a</mi>
|
||
<mi>b</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>a</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
<mo>+</mo>
|
||
<mi>f</mi>
|
||
<mrow>
|
||
<mo>(</mo>
|
||
<mi>b</mi>
|
||
<mo>)</mo>
|
||
</mrow>
|
||
</mrow>
|
||
</math>
|
||
whenever <math><mi>a</mi></math> and <math><mi>b</mi></math> are
|
||
coprime. We can sieve these functions in nearly the exact same
|
||
manner as multiplicative functions (e.g. the
|
||
<a href="https://en.wikipedia.org/wiki/Prime_omega_function">prime omega functions</a>
|
||
in the demo are additive). When we get to the code, you'll see the
|
||
provided functions take a boolean indicating whether the function
|
||
is additive to apply the proper logic.</p>
|
||
</section>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>The algorithm</h2>
|
||
|
||
<p>Here's the routine for sieving multiplicative functions:</p>
|
||
<figure>
|
||
<pre><code>function sieveSegment(l, r, f, primes, additive) {
|
||
const [sieve, quo] = initArrays(l, r, additive);
|
||
|
||
for (let i = 0; i < primes.length; i++) {
|
||
if (i*i > r) {
|
||
break;
|
||
}
|
||
|
||
if (!primes[i]) {
|
||
continue;
|
||
}
|
||
|
||
sievePowers(i, l, r, f, sieve, quo, additive);
|
||
}
|
||
|
||
unusuals(f, sieve, quo, additive);
|
||
|
||
return sieve;
|
||
}</code></pre>
|
||
<figcaption>This function performs a segmented sieve on any
|
||
multiplicative (or additive) function.</figcaption>
|
||
</figure>
|
||
|
||
<p><code>l</code> and <code>r</code> are the lower and upper bounds
|
||
for the sieve, respectively. <code>f</code> is the function we are
|
||
sieving. <code>primes</code> is an array indicating all the prime
|
||
numbers up to the square root of <code>r</code>, which we can
|
||
precompute with the standard sieve. Finally, <code>additive</code> is
|
||
a boolean indicating if the function is additive or
|
||
multiplicative.</p>
|
||
|
||
<p>The first step of the function is to initialize two arrays of size
|
||
<code>r - l</code> called <code>sieve</code> and <code>quo</code>.
|
||
<code>sieve</code> is what ultimately gets returned and stores the
|
||
values of the function we're sieving. <code>quo</code> is an internal
|
||
array we use to finalize the values of sieve at the very end of the
|
||
function.</p>
|
||
|
||
<figure>
|
||
<pre><code>function initArrays(l, r, additive) {
|
||
const sieve = Array(r - l);
|
||
const quo = Array(r - l);
|
||
for (let i = 0; i < r-l; i++) {
|
||
quo[i] = l + i;
|
||
if (additive) {
|
||
sieve[i] = 0;
|
||
} else {
|
||
sieve[i] = 1;
|
||
}
|
||
}
|
||
|
||
return [sieve, quo];
|
||
}</code></pre>
|
||
<figcaption>The <code>initArrays</code> function initializes arrays
|
||
that keep track of the value of the function we're sieving and
|
||
the running quotients.</figcaption>
|
||
</figure>
|
||
|
||
<p>Say we want to calculate the number of divisors function from 30
|
||
to 60. After <code>initArrays</code> runs, here's what the
|
||
<code>sieve</code> and <code>quo</code> arrays look like:</p>
|
||
|
||
<table>
|
||
<caption>
|
||
Initial values of <code>sieve</code> and <code>quo</code>
|
||
</caption>
|
||
<tbody>
|
||
<tr>
|
||
<th>n</th>
|
||
<td>30</td>
|
||
<td>31</td>
|
||
<td>32</td>
|
||
<td>33</td>
|
||
<td>34</td>
|
||
<td>35</td>
|
||
<td>36</td>
|
||
<td>37</td>
|
||
<td>38</td>
|
||
<td>39</td>
|
||
<td>40</td>
|
||
<td>41</td>
|
||
<td>42</td>
|
||
<td>43</td>
|
||
<td>44</td>
|
||
<td>45</td>
|
||
<td>46</td>
|
||
<td>47</td>
|
||
<td>48</td>
|
||
<td>49</td>
|
||
<td>50</td>
|
||
<td>51</td>
|
||
<td>52</td>
|
||
<td>53</td>
|
||
<td>54</td>
|
||
<td>55</td>
|
||
<td>56</td>
|
||
<td>57</td>
|
||
<td>58</td>
|
||
<td>59</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>sieve</code></th>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>quo</code></th>
|
||
<td>30</td>
|
||
<td>31</td>
|
||
<td>32</td>
|
||
<td>33</td>
|
||
<td>34</td>
|
||
<td>35</td>
|
||
<td>36</td>
|
||
<td>37</td>
|
||
<td>38</td>
|
||
<td>39</td>
|
||
<td>40</td>
|
||
<td>41</td>
|
||
<td>42</td>
|
||
<td>43</td>
|
||
<td>44</td>
|
||
<td>45</td>
|
||
<td>46</td>
|
||
<td>47</td>
|
||
<td>48</td>
|
||
<td>49</td>
|
||
<td>50</td>
|
||
<td>51</td>
|
||
<td>52</td>
|
||
<td>53</td>
|
||
<td>54</td>
|
||
<td>55</td>
|
||
<td>56</td>
|
||
<td>57</td>
|
||
<td>58</td>
|
||
<td>59</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p>Next, we'll call the following functions for every prime number in
|
||
the <code>primes</code> array. This is where the function spends most
|
||
of its time.</p>
|
||
|
||
<figure>
|
||
<pre><code>function sievePowers(p, l, r, f, sieve, quo, additive) {
|
||
let i = 1;
|
||
for (let q = p; q < r; q *= p) {
|
||
const y = f(p, i);
|
||
sieveMultiples(p, q, l, r, y, sieve, quo, additive);
|
||
i++;
|
||
}
|
||
}
|
||
|
||
function sieveMultiples(p, q, l, r, y, sieve, quo, additive) {
|
||
const low = Math.ceil(l / q);
|
||
for (let i = low * q; i < r; i += q) {
|
||
if (i % (p*q) === 0) {
|
||
continue;
|
||
}
|
||
|
||
if (additive) {
|
||
sieve[i - l] += y;
|
||
} else {
|
||
sieve[i - l] *= y;
|
||
}
|
||
|
||
quo[i - l] /= q
|
||
}
|
||
}</code></pre>
|
||
<figcaption>These functions are where most of the computation
|
||
happens.</figcaption>
|
||
</figure>
|
||
|
||
<p>In short, these functions evaluate our function at prime powers
|
||
only, then update the values in the <code>sieve</code> array for
|
||
non-prime powers using the multiplicative property. We also update
|
||
the <code>quo</code> array by dividing out the factors we find along
|
||
the way.</p>
|
||
|
||
<p>Here's what the arrays will look like after sieving with 2:</p>
|
||
<table>
|
||
<caption>
|
||
Values of <code>sieve</code> and <code>quo</code> after sieving 2
|
||
</caption>
|
||
<tbody>
|
||
<tr>
|
||
<th>n</th>
|
||
<td>30</td>
|
||
<td>31</td>
|
||
<td>32</td>
|
||
<td>33</td>
|
||
<td>34</td>
|
||
<td>35</td>
|
||
<td>36</td>
|
||
<td>37</td>
|
||
<td>38</td>
|
||
<td>39</td>
|
||
<td>40</td>
|
||
<td>41</td>
|
||
<td>42</td>
|
||
<td>43</td>
|
||
<td>44</td>
|
||
<td>45</td>
|
||
<td>46</td>
|
||
<td>47</td>
|
||
<td>48</td>
|
||
<td>49</td>
|
||
<td>50</td>
|
||
<td>51</td>
|
||
<td>52</td>
|
||
<td>53</td>
|
||
<td>54</td>
|
||
<td>55</td>
|
||
<td>56</td>
|
||
<td>57</td>
|
||
<td>58</td>
|
||
<td>59</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>sieve</code></th>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>6</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>3</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>4</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>3</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>5</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>3</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>4</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>quo</code></th>
|
||
<td>15</td>
|
||
<td>31</td>
|
||
<td>1</td>
|
||
<td>33</td>
|
||
<td>17</td>
|
||
<td>35</td>
|
||
<td>9</td>
|
||
<td>37</td>
|
||
<td>19</td>
|
||
<td>39</td>
|
||
<td>5</td>
|
||
<td>41</td>
|
||
<td>21</td>
|
||
<td>43</td>
|
||
<td>11</td>
|
||
<td>45</td>
|
||
<td>23</td>
|
||
<td>47</td>
|
||
<td>3</td>
|
||
<td>49</td>
|
||
<td>25</td>
|
||
<td>51</td>
|
||
<td>13</td>
|
||
<td>53</td>
|
||
<td>27</td>
|
||
<td>55</td>
|
||
<td>7</td>
|
||
<td>57</td>
|
||
<td>29</td>
|
||
<td>59</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p>And here's what the arrays will look like after subsequently
|
||
sieving with 3:</p>
|
||
<table>
|
||
<caption>
|
||
Values of <code>sieve</code> and <code>quo</code> after sieving 3
|
||
</caption>
|
||
<tbody>
|
||
<tr>
|
||
<th>n</th>
|
||
<td>30</td>
|
||
<td>31</td>
|
||
<td>32</td>
|
||
<td>33</td>
|
||
<td>34</td>
|
||
<td>35</td>
|
||
<td>36</td>
|
||
<td>37</td>
|
||
<td>38</td>
|
||
<td>39</td>
|
||
<td>40</td>
|
||
<td>41</td>
|
||
<td>42</td>
|
||
<td>43</td>
|
||
<td>44</td>
|
||
<td>45</td>
|
||
<td>46</td>
|
||
<td>47</td>
|
||
<td>48</td>
|
||
<td>49</td>
|
||
<td>50</td>
|
||
<td>51</td>
|
||
<td>52</td>
|
||
<td>53</td>
|
||
<td>54</td>
|
||
<td>55</td>
|
||
<td>56</td>
|
||
<td>57</td>
|
||
<td>58</td>
|
||
<td>59</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>sieve</code></th>
|
||
<td>4</td>
|
||
<td>1</td>
|
||
<td>6</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>9</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>4</td>
|
||
<td>1</td>
|
||
<td>4</td>
|
||
<td>1</td>
|
||
<td>3</td>
|
||
<td>3</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>10</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>3</td>
|
||
<td>1</td>
|
||
<td>8</td>
|
||
<td>1</td>
|
||
<td>4</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>quo</code></th>
|
||
<td>5</td>
|
||
<td>31</td>
|
||
<td>1</td>
|
||
<td>11</td>
|
||
<td>17</td>
|
||
<td>35</td>
|
||
<td>1</td>
|
||
<td>37</td>
|
||
<td>19</td>
|
||
<td>13</td>
|
||
<td>5</td>
|
||
<td>41</td>
|
||
<td>7</td>
|
||
<td>43</td>
|
||
<td>11</td>
|
||
<td>5</td>
|
||
<td>23</td>
|
||
<td>47</td>
|
||
<td>1</td>
|
||
<td>49</td>
|
||
<td>25</td>
|
||
<td>17</td>
|
||
<td>13</td>
|
||
<td>53</td>
|
||
<td>1</td>
|
||
<td>55</td>
|
||
<td>7</td>
|
||
<td>19</td>
|
||
<td>29</td>
|
||
<td>59</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p>After we've gone through the entire <code>primes</code> array, the
|
||
two arrays will look like this:</p>
|
||
<table>
|
||
<caption>
|
||
Values of <code>sieve</code> and <code>quo</code> just before
|
||
calling <code>unusuals</code>
|
||
</caption>
|
||
<tbody>
|
||
<tr>
|
||
<th>n</th>
|
||
<td>30</td>
|
||
<td>31</td>
|
||
<td>32</td>
|
||
<td>33</td>
|
||
<td>34</td>
|
||
<td>35</td>
|
||
<td>36</td>
|
||
<td>37</td>
|
||
<td>38</td>
|
||
<td>39</td>
|
||
<td>40</td>
|
||
<td>41</td>
|
||
<td>42</td>
|
||
<td>43</td>
|
||
<td>44</td>
|
||
<td>45</td>
|
||
<td>46</td>
|
||
<td>47</td>
|
||
<td>48</td>
|
||
<td>49</td>
|
||
<td>50</td>
|
||
<td>51</td>
|
||
<td>52</td>
|
||
<td>53</td>
|
||
<td>54</td>
|
||
<td>55</td>
|
||
<td>56</td>
|
||
<td>57</td>
|
||
<td>58</td>
|
||
<td>59</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>sieve</code></th>
|
||
<td>8</td>
|
||
<td>1</td>
|
||
<td>6</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>4</td>
|
||
<td>9</td>
|
||
<td>1</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>8</td>
|
||
<td>1</td>
|
||
<td>8</td>
|
||
<td>1</td>
|
||
<td>3</td>
|
||
<td>6</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
<td>10</td>
|
||
<td>3</td>
|
||
<td>6</td>
|
||
<td>2</td>
|
||
<td>3</td>
|
||
<td>1</td>
|
||
<td>8</td>
|
||
<td>2</td>
|
||
<td>8</td>
|
||
<td>2</td>
|
||
<td>2</td>
|
||
<td>1</td>
|
||
</tr>
|
||
<tr>
|
||
<th><code>quo</code></th>
|
||
<td>1</td>
|
||
<td>31</td>
|
||
<td>1</td>
|
||
<td>11</td>
|
||
<td>17</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>37</td>
|
||
<td>19</td>
|
||
<td>13</td>
|
||
<td>1</td>
|
||
<td>41</td>
|
||
<td>1</td>
|
||
<td>43</td>
|
||
<td>11</td>
|
||
<td>1</td>
|
||
<td>23</td>
|
||
<td>47</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>1</td>
|
||
<td>17</td>
|
||
<td>13</td>
|
||
<td>53</td>
|
||
<td>1</td>
|
||
<td>11</td>
|
||
<td>1</td>
|
||
<td>19</td>
|
||
<td>29</td>
|
||
<td>59</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>At this point, there's one last step. Numbers can have up to one
|
||
prime factor greater than their own square root. When that happens,
|
||
they're called
|
||
<a href="https://en.wikipedia.org/wiki/Unusual_number">unusual numbers</a>.<sup>3</sup>
|
||
But since we only require the <code>primes</code> array to hold
|
||
primes up to the square root of <code>r</code>, any unusual number
|
||
will have one last prime factor that we never evaluated. Fortunately,
|
||
we can identify those numbers easily using the <code>quo</code>
|
||
array, so we just need to do one more pass through that array to
|
||
finalize everything.</p>
|
||
|
||
<figure>
|
||
<pre><code>function unusuals(f, sieve, quo, additive) {
|
||
for (let i = 0; i < quo.length; i++) {
|
||
const q = quo[i];
|
||
if (q === 1) {
|
||
continue;
|
||
}
|
||
|
||
if (additive) {
|
||
sieve[i] += f(q, 1);
|
||
} else {
|
||
sieve[i] *= f(q, 1);
|
||
}
|
||
}
|
||
}</code></pre>
|
||
<figcaption>The last step is to finalize the values of any unusual
|
||
numbers (numbers n with a prime factor greater than
|
||
sqrt(n)).</figcaption>
|
||
</figure>
|
||
|
||
<section>
|
||
<h3>Analysis</h3>
|
||
<p>It's pretty easy to see that <code>initArrays</code> and
|
||
<code>unusuals</code> are
|
||
<math><mi>O</mi><mo>(</mo><mi>r</mi><mo>-</mo><mi>l</mi><mo>)</mo></math>.<sup>4</sup>
|
||
Analyzing <code>sievePowers</code> and its impact is a bit more
|
||
involved.</p>
|
||
|
||
<p>Looking at <code>sieveMultiples</code>, its main loop performs a
|
||
fixed number of arithmetic operations
|
||
<math>
|
||
<mrow>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="true">(</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mi>q</mi>
|
||
</mfrac>
|
||
<mo fence="true" form="postfix" stretchy="true">)</mo>
|
||
</mrow>
|
||
</mrow>
|
||
</math>
|
||
times. <code>sieveMultiples</code>, in turn, is called by
|
||
<code>sievePowers</code> at values of
|
||
<math><mi>q</mi><mo>=</mo><mi>p</mi></math>,
|
||
<math><mi>q</mi><mo>=</mo><msup><mi>p</mi><mn>2</mn></msup></math>,
|
||
<math><mi>q</mi><mo>=</mo><msup><mi>p</mi><mn>3</mn></msup></math>,
|
||
and so on until <math><msup><mi>p</mi><mi>k</mi></msup></math>
|
||
exceeds <math><mi>r</mi></math>. This means the total time
|
||
complexity of <code>sievePowers</code> is<sup>4</sup></p>
|
||
|
||
<div class="math-block">
|
||
<math display="block" class="tml-display" style="display:block math;">
|
||
<mrow>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="true">(</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mi>p</mi>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<msup>
|
||
<mi>p</mi>
|
||
<mn class="tml-med-pad">2</mn>
|
||
</msup>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<msup>
|
||
<mi>p</mi>
|
||
<mn class="tml-med-pad">3</mn>
|
||
</msup>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mo>…</mo>
|
||
<mo fence="true" form="postfix" stretchy="true">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="true">(</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mi>p</mi>
|
||
</mfrac>
|
||
<mo fence="true" form="postfix" stretchy="true">)</mo>
|
||
</mrow>
|
||
</mrow>
|
||
</math>
|
||
</div>
|
||
|
||
<p>With this in mind, we can evaluate <code>sieveSegment</code>,
|
||
which calls <code>sievePowers</code> for every prime less than the
|
||
square root of <code>r</code>. Thanks to Euler, we know that
|
||
<a href="https://en.wikipedia.org/wiki/Divergence_of_the_sum_of_the_reciprocals_of_the_primes">the sum of the reciprocal of primes up to <math><mi>k</mi></math></a>
|
||
is asymptotic to
|
||
<math>
|
||
<mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<mi>k</mi>
|
||
</mrow>
|
||
</math>.
|
||
Therefore, the time complexity of <code>sieveSegment</code> is</p>
|
||
|
||
<div class="math-block">
|
||
<math display="block" class="tml-display" style="display:block math;">
|
||
<mrow>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="true">(</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mn>2</mn>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mn>3</mn>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mfrac>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
</mrow>
|
||
<mn>5</mn>
|
||
</mfrac>
|
||
<mo>+</mo>
|
||
<mo>…</mo>
|
||
<mo fence="true" form="postfix" stretchy="true">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<msqrt>
|
||
<mrow>
|
||
<mi>r</mi>
|
||
<mspace width="0pt" height="0.5em"></mspace>
|
||
</mrow>
|
||
</msqrt>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mo>=</mo>
|
||
<mi>O</mi>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mrow>
|
||
<mo fence="true" form="prefix" stretchy="false">(</mo>
|
||
<mi>r</mi>
|
||
<mo>−</mo>
|
||
<mi>l</mi>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<mrow>
|
||
<mi>log</mi>
|
||
<mo></mo>
|
||
<mspace width="0.1667em"></mspace>
|
||
</mrow>
|
||
<mi>r</mi>
|
||
<mo fence="true" form="postfix" stretchy="false">)</mo>
|
||
</mrow>
|
||
</mrow>
|
||
</math>
|
||
</div>
|
||
|
||
<p>This is the same complexity as the method given
|
||
<a href="https://cp-algorithms.com/algebra/phi-function.html">here</a>
|
||
for sieving the totient function.</p>
|
||
</section>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Final thoughts</h2>
|
||
<p>This all ends up being more lines of code than writing a single
|
||
sieve for, say, totient or the number of divisors. However, if you
|
||
want to sieve multiple functions, it can be tricky to make the right
|
||
modifications to an existing sieve to work with a different
|
||
function.</p>
|
||
|
||
<p>In contrast, with this approach, you can use the same routine and
|
||
simply pass a different function as an argument. For example, here's
|
||
all the functions available in the above demo:</p>
|
||
|
||
<figure>
|
||
<pre><code>function numDivisors(p, k) {
|
||
return k + 1;
|
||
}
|
||
|
||
function sumDivisors(p, k) {
|
||
return (p**(k+1) - 1) / (p - 1)
|
||
}
|
||
|
||
function totient(p, k) {
|
||
return p**(k-1) * (p - 1);
|
||
}
|
||
|
||
function radical(p, k) {
|
||
return p;
|
||
}
|
||
|
||
function littleOmega(p, k) {
|
||
return 1;
|
||
}
|
||
|
||
function bigOmega(p, k) {
|
||
return k;
|
||
}
|
||
|
||
function mobius(p, k) {
|
||
if (k === 1) {
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}</code></pre>
|
||
<figcaption>Functions that can be sieved in the demo.</figcaption>
|
||
</figure>
|
||
|
||
<p>Many multiplicative and additive functions have simple formulas
|
||
for prime powers, which makes adding a new function to sieve very
|
||
easy with this approach.</p>
|
||
</section>
|
||
|
||
<section>
|
||
<h2>Footnotes</h2>
|
||
<ol id="footnote-list">
|
||
<li><sup>1</sup> This is because the only numbers that divide
|
||
<math><msup><mi>p</mi><mi>k</mi></msup></math> are
|
||
<math><mn>1</mn></math>, <math><mi>p</mi></math>,
|
||
<math><msup><mi>p</mi><mn>2</mn></msup></math>, ..., up to
|
||
<math><msup><mi>p</mi><mi>k</mi></msup></math>, which is
|
||
<math><mi>k</mi><mo>+</mo><mn>1</mn></math> total numbers.</li>
|
||
|
||
<li><sup>2</sup> You might be thinking at this point that factoring
|
||
is <em>also</em> not very efficient. Sit tight; this is only an
|
||
example to get you comfortable with multiplicative functions.
|
||
We're going to take advantage of this property later.</li>
|
||
|
||
<li><sup>3</sup> "Unusual" is actually a bit of a misnomer - most
|
||
numbers have a prime factor greater than their square root, so
|
||
unusual numbers are actually more common than so-called "usual"
|
||
numbers!</li>
|
||
|
||
<li><sup>4</sup> Well, technically <code>unusuals</code> and
|
||
<code>sievePowers</code> are also dependent on the time
|
||
complexity of the function we're sieving, but generally that
|
||
complexity isn't too bad, so I'm going to gloss over that bit.</li>
|
||
</ol>
|
||
</section>
|
||
</article>
|
||
</main>
|
||
|
||
<footer>
|
||
<p id="copyright">© 2026 filifa. This page is licensed under <a href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</a> and the <a href="https://spdx.org/licenses/0BSD.html">BSD Zero Clause license</a>.</p>
|
||
</footer>
|
||
|
||
<script src="sieves.js"></script>
|
||
</body>
|
||
</html>
|