2026-04-15 18:15:08 -04:00
<!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 >
2026-04-15 20:26:19 -04:00
< p > Furthermore, a similar approach can be used to "sieve" the values of
2026-04-15 18:15:08 -04:00
< 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 >