These are the human proofs of Lucy_Hedgehog prime counting, at full length.
The Lucy_Hedgehog method returns pi(x) full
This proof shows: correctness.
For m≥2 let lpf(m) be the least prime factor of m; a composite m satisfies lpf(m)2≤m, because m=lpf(m)⋅k with k≥2, and every prime factor of k is at least lpf(m). For q≥1 and v≥0 let Sq(v)=#{2≤n≤v:n prime or lpf(n)>q}.
If x<2, line L1 returns 0, which is π(x). Assume x≥2, so r=⌊x⌋≥1, and let V={⌊x/k⌋:k≥1}.
Facts about V. (a) For v∈V and an integer p≥1, ⌊v/p⌋∈V or ⌊v/p⌋=0: if v=⌊x/k⌋, then ⌊v/p⌋=⌊x/(kp)⌋, since writing x=kpq+s with 0≤s<kp gives ⌊x/k⌋=pq+⌊s/k⌋ with ⌊s/k⌋<p. (b) Every integer 1≤v≤r is in V. Let k=⌊x/v⌋; then k≥x/v−1 and, since v2≤x, k≥v. As k≤x/v, ⌊x/k⌋≥v. If ⌊x/k⌋≥v+1, then k(v+1)≤x, so x/v−1<k≤x/(v+1), which gives x<v(v+1); but k≥v and k(v+1)≤x give v(v+1)≤x, a contradiction. So ⌊x/k⌋=v.
Invariant. For p=2,…,r+1: at the start of round p of line L4 (or after the loop, for p=r+1), S[v]=Sp−1(v) for all v∈V.
Base. For p=2, line L3 sets S[v]=v−1=#{2≤n≤v}=S1(v), since every n≥2 has lpf(n)≥2>1.
Step. Assume the invariant at the start of round p, with 2≤p≤r. By fact (b), p and p−1 lie in V.
First, Sp−1(p)−Sp−1(p−1) is 1 if p counts in Sp−1(p) and 0 otherwise, that is, 1 if and only if p is prime or lpf(p)>p−1. As lpf(p)≤p, lpf(p)>p−1 means lpf(p)=p, that is, p is prime. So the test of line L5 holds exactly when p is composite.
Case p composite. No n has lpf(n)=p, since lpf(n) is prime. So lpf(n)>p−1 if and only if lpf(n)>p, hence Sp=Sp−1, and line L5 leaves S unchanged, as required.
Case p prime. Comparing the definitions, Sp−1(v)−Sp(v) is the number of 2≤n≤v that are not prime and have lpf(n)=p; call this Cp(v). If v<p2, then Cp(v)=0, because a composite n with lpf(n)=p has n≥p2. Let v≥p2 and w=⌊v/p⌋≥p. The map n↦k=n/p is a bijection from the composites n≤v with lpf(n)=p to the integers 2≤k≤w with lpf(k)≥p: if n=pk is such a composite, then k≥2, k≤v/p, and every prime factor of k divides n, hence is at least p; conversely, if 2≤k≤w has lpf(k)≥p, then n=pk≤v is composite with lpf(n)=p. The integers 2≤k≤w that count in Sp−1(w) are those that are prime or have lpf(k)≥p; the ones among them with lpf(k)<p are exactly the primes below p, and all π(p−1) of them are at most p−1<w. Hence Cp(v)=Sp−1(w)−π(p−1). Finally Sp−1(p−1)=π(p−1), because every 2≤n≤p−1 has lpf(n)≤n≤p−1, so only the primes count. By the invariant, line L6 sets c=Sp−1(p−1)=π(p−1).
Line L7 visits the v∈V with v≥p2 in decreasing order. When it updates v, it reads S[w] with w=⌊v/p⌋∈V by fact (a) (w≥p≥1), and w<v; every value updated earlier in this round is larger than v, so S[w] still holds Sp−1(w). Also p−1<p2, so S[p−1] is not changed in this round, and c is correct throughout. So line L8 sets S[v]=Sp−1(v)−(Sp−1(w)−π(p−1))=Sp−1(v)−Cp(v)=Sp(v). Each v is updated once, from its old value Sp−1(v). The values v<p2 are not touched and keep Sp−1(v)=Sp(v). So the invariant holds at the start of round p+1.
Conclusion. After the loop, S[x]=Sr(x) (and x∈V with k=1). Every composite n≤x has lpf(n)≤n≤x, hence lpf(n)≤r, so it does not count in Sr(x), while every prime n≤x does. So line L9 returns Sr(x)=π(x).
The two arrays. The implementation stores S[v] at small[v] for v≤r and at large[k] for v=⌊x/k⌋, 1≤k≤r. Every v∈V with v>r has such a k, namely k=⌊x/v⌋: from v=⌊x/j⌋ we get j≤x/v, so j≤k and ⌊x/k⌋≤v, while k≤x/v gives ⌊x/k⌋≥v; and k≤x/v<x/x=x, so k≤r. The loop over large for k=1,2,… followed by the loop over small from r down is a pass in decreasing order of v, the cell read for ⌊x/(kp)⌋ is large[kp] if kp≤r and small[x // (kp)] otherwise, and in the latter case ⌊x/(kp)⌋≤r holds, because kp≥r+1>x. A value stored in both arrays receives the same update in both, from the same old values, so the two copies stay equal. ■