Prime Counting Functions #6
Replies: 11 comments 45 replies
-
Hi Gordon! Welcome to my project, I'm very happy to have you here! :)
No, my goal is to offer a prime library that's easy to use in NodeJS + Web, using TypeScript. That's why I focus in my code examples more on use of RXJS, because it is the best today for handling sequences inside NodeJS and Web. Also, I want something that's actually efficient and fast, so I ended up adding quite a few functions to it at this point - see the API at the bottom of the main page. I agree with all you say there about possible further optimizations. This library is still very young, and I keep adding those optimizations as I find them. The last things I added though were functions countPrimes and countPrimesApprox, which think are quite good, at least in their first iteration :) Also, worth noting, the library just has been renamed into a more appropriate I am currently looking into a possible implementation for nthPrimeApprox. I believe that Christian Axler's work is the best in this area today. |
Beta Was this translation helpful? Give feedback.
-
I did try to convert it into a generator, but got lost in the end.
Absolutely, thank you!!!
And yet it nailed the competition, with blazing-fast results (see the bottom benchmark) 😃 It was only beaten by some heavily optimized C++ code, which used 128-bit arithmetic (cheater) 😄
Yes, certainly! My library includes 100% test coverage, so if you also want to tweak something and PR it - you won't break things, it's all thoroughly tested ;) |
Beta Was this translation helpful? Give feedback.
-
That's much slower than my current implementation - see the bottom of the benchmarks:
|
Beta Was this translation helpful? Give feedback.
-
As interspersed below:
On Mon, Nov 1, 2021, 11:49 Vitaly Tomilov ***@***.***> wrote:
The program can count the primes to a billion in about a half a second,
although it takes about three seconds to enumerate over the primes in this
range, it counts the primes to 1e10 in about four seconds, and to 1e11 in
about 50 seconds. Although it starts to lose efficiency above 1e12 (about
ten minutes)
That's much slower than my current implementation - see the bottom of the
benchmarks <https://github.com/vitaly-t/prime-lib/tree/main/benchmarks>:
- 1e10 => 166ms
- 1e11 => 1,545ms
- 1e12 => 16s
You realize that the bottom benchmarks aren't a sieve but a prime counting
function and thus can never enumerate over the given range of primes, don't
you? My posted code is about as fast as a **sieve** can run in JavaScript
and as posted can be a prime generator. My next post will be a prime
counting function that will be much faster than what you have, especially
as the upper limit goes up, as the time cost will only grow at less then a
hundred times for every thousand times the upper limit is increased.
If you look at the code for your current basic prime counting function,
you'll notice it contains a basic SoE which is used to sieve to the range
of the upper limit to the two thirds power, and part of the reason it
doesn't gain the performance it should at high ranges is that this basic
sieve loses its efficiency very quickly with range. Thus, one needs a fast
sieve in order to implement a fast prime counting function, which is why I
have provided the fast sieve first. As the code I just provided can sieve
to ten billion (1e10) in just something like ten seconds, it opens the door
to running the rest of the LMO prime counting function in just a little
more, so being able to count the primes to 1e15 in under a minute. **Your
current prime counting function takes something like eight hours to do
that!**
Your current prime counting function is alright for smallish upper limits
as it has low overheads, but as the upper limit grows above about 1e10, it
will lose out to a more advanced technique as I will use in the code I will
send you in a few days to a week. This is analogous to the simple sieves
that your library currently contains being faster for ranges up to a
million or ten million due to low overhead, but then losing out for ranges
above that.
If you are serious about supporting non trivial ranges, you need to move
past the basic solutions.
Regards, Gordon
…
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#6 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACRTMTP5CSENYJ47XRNRW3TUJYL6NANCNFSM5GRKXDDA>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
Beta Was this translation helpful? Give feedback.
-
Rather than comparing those results with my true wheel-factorized page-segmented bit-packed Sieve of Eratosthenes sieve, you should be comparing it with a properly implemented prime counting function, which that is not. My answer on the same SO thread where you found that code is a properly implement prime counting function and runs in the following times on my machine, which machine is quite a bit slower than yours if comparing the above results is indicative, as it took 50 seconds to use your prime counting function to count the primes to 1e12 on an Intel Skylake i5-6500 running at 3.6 GHz (single threaded boost):
There are several reasons my prime counting function is so much faster, as follows:
I developed the code as a node.js console application and then converted it to a web page snippet to make it convenient for casual SO browsers to run the code inside the answer; it would be easy to strip the web page stuff out of the code to get back to a node.js console application version, or I can provide that from my original source. Before doing that, I want to spend another few days trying to adapt the more sophisticated SoE to the code to see if it speeds it up by about a factor of two as I expect (and perhaps provide that new code as an alternate answer on the SO thread, as that answer is full). That is what I've been trying to teach you: if you need to actually enumerate the found primes, you need a sieve such as my SoE version I provided previously; if you only need the total count of primes over a range, you can use a properly implemented prime counting function which is exponential powers faster and thus usable to much larger limits. Actually, even if you need the sum of the primes, you can also use these prime counting function numerical analysis techniques to obtain that sum in times exponentially faster than doing if by sieving, but the code is even more complex to implement. Once one has a fast prime counting function, finding the nth prime is about as fast: One estimates the point to which one has to calculate to obtain the I think that if I get you to that point, I probably don't have more to contribute as I really don't enjoy coding in JavaScript. |
Beta Was this translation helpful? Give feedback.
-
Hi @vitaly-t @GordonBGood just curious if you guys understand what's going on in this implementation of Meissel-Lehmer . It's about 10x faster than more comprehensible implementations of Meissel-Lehmer like say this one . |
Beta Was this translation helpful? Give feedback.
-
I'm not doing anything for this project presently, but I did update it just now ;) |
Beta Was this translation helpful? Give feedback.
-
I've worked some more with the "magic" algorithm and see that it is not Meissel, Meissel-Lehmer, or LMO, or otherwise it would require a calculation of the number of primes to the cube root of the range. It also isn't Legendre exactly as the execution complexity doesn't match what the implementation of that usually is; rather, it is an "improved" Legendre algorithm in a similar way to that LMO is an improved Meissel algorithm that differs in greatly improved computational complexity. The nice thing about this algorithm is that it is relatively easy to implement as to required LOC, but it still uses memory at the same rate as Legendre and Meissel-Lehmer. The way that recursive determination of the "phi" function is eliminated is by using the principle of "partial sieving", where, in the first sieving loop where the base prime factors of three and up to the quad root of the range are culled, upon culling for a given base prime value, it then scans across all the remaining non-culled values at that point which will represent all of the So at the end of the first major loop, the sieve of odd values from one up to the square root of the range as been culled (although that will never be used again after the first major loop), the "smalls" array contains the counts minus one up to each of the indices for the odd numbers from 1 up to the square root of the range as in 0 for 1, 1 for 3, 2 for 5, 3 for 7, 3 for 9 (no increase as 9 is not prime), 4 for 11, etc., the "roughs" array will contain 1, first prime after the last prime up to the quad root of the range, and succeeding primes up to the square root of the range, and the "larges" will contain the included/excluded counts up to this point, with a new size variable containing the usable length of the "roughs" and "larges" arrays, and there being a prime count variable containing one less than the number of primes found up to the quad root of the range. At this point all of the succeeding "larges" values are subtracted from the first value and the "roughs" array never needs to be used again. The result can then be adjusted slightly for the exact number of values and primes found to get from a "phi" to a "pi". Finally, the second major loop adds the counts and subtracts the small adjustment for those combinations of the product of the "roughs" primes above the quad root and less than the square root whose product is larger than the square root of the range but with only the product of two different primes used (and can never be the same prime). The reason that this algorithm reduces execution complexity as compared to the standard Legendre algorithm is the use of partial sieving, which finds the products of many primes in one loop pass instead of many successive recursions for the conventional implementation, just as LMO uses partial sieving for a similar (but greater) gain. With the somewhat complex logic determining that the product of (just two) primes qualify being the reason this algorithm also saves operations for loops that can't contribute to the included/excluded sums just as a similar simple optimization can make the Legendre algorithm even usable without memoization, else having an exponential time complexity. This algorithm should really be written up and published as per the LMO format, although the algorithm is considerably simpler than that of LMO; OTOH, the only use this algorithm will likely ever get is for the purposes of competitive programming where contributors don't want to submit the more complex LMO or better algorithms for quite trivial ranges such as 1e11 as used for such contributions... |
Beta Was this translation helpful? Give feedback.
-
@vitaly-t, @ishandutta2007, there has been some activity on the StackOverflow thread where my JavaScript snippet is posted identifying a bug that shows up when the counting range is about the exact cube of a prime; the following is the node.js code that fixes this for use here in the same way: "use strict";
const MAXVALUE = 1e11; // 9007199254740991; // 2**53 - 1
// creates a function returning a lazily memoized value from a thunk...
function lazy(thunk) {
let value = undefined;
return function() {
if (value === undefined) { value = thunk(); thunk = null; }
return value;
}
}
// a page-segmented odds-only bit-packed Sieve of Eratosthenes;
const PGSZBITS = 262144; // about CPU l1 cache size in bits (power of two)
const CLUT = function () { // fast "pop count" Counting Look Up Table...
const arr = new Uint8Array(65536);
for (let i = 0; i < 65536; ++i) {
let nmbts = 0 | 0; let v = i;
while (v > 0) { ++nmbts; v &= (v - 1) | 0; }
arr[i] = nmbts | 0; }
return arr;
}();
function countPageFromTo(bitstrt, bitlmt, sb) {
const fst = bitstrt >> 5; const lst = bitlmt >> 5;
const pg = new Uint32Array(sb.buffer);
let v0 = (pg[fst] | ((0xFFFFFFFF >>> 0) << (bitstrt & 31))) >>> 0;
let cnt = ((lst - fst) << 5) + CLUT[v0 & 0xFFFF]; cnt += CLUT[v0 >>> 16];
for (let i = fst | 0; i < lst; ++i) {
let v = pg[i] >>> 0;
cnt -= CLUT[v & 0xFFFF]; cnt -= CLUT[v >>> 16];
}
let v1 = (pg[lst] | ((0xFFFFFFFE >>> 0) << (bitlmt & 31))) >>> 0;
cnt -= CLUT[v1 & 0xFFFF]; cnt -= CLUT[v1 >>> 16]; return cnt | 0;
}
function partialSievePage(lwi, bp, sb) {
const btsz = sb.length << 3;
let s = Math.trunc((bp * bp - 3) / 2); // compute the start index...
if (s >= lwi) s -= lwi; // adjust start index based on page lower limit...
else { // for the case where this isn't the first prime squared instance
let r = ((lwi - s) % bp) >>> 0;
s = (r != (0 >>> 0) ? bp - r : 0) >>> 0; }
if (bp <= 32) {
for (let slmt = Math.min(btsz, s + (bp << 3)); s < slmt; s += bp) {
const shft = s & 7; const msk = ((1 >>> 0) << shft) >>> 0;
for (let c = s >> 3, clmt = sb.length; c < clmt | 0; c += bp)
sb[c] |= msk; } }
else
for (let slmt = sb.length << 3; s < slmt; s += bp)
sb[s >> 3] |= ((1 >>> 0) << (s & 7)) >>> 0;
}
function partialSieveCountPage(lwi, bp, cntarr, sb) {
const btsz = sb.length << 3; let cullcnt = 0;
let s = Math.trunc((bp * bp - 3) / 2); // compute the start index...
if (s >= lwi) // adjust start index based on page lower limit...
s -= lwi;
else { // for the case where this isn't the first prime squared instance
let r = ((lwi - s) % bp) >>> 0;
s = (r != (0 >>> 0) ? bp - r : 0) >>> 0; }
if (bp <= 32) {
for (let slmt = Math.min(btsz, s + (bp << 3)); s < slmt; s += bp) {
const shft = s & 7; const msk = ((1 >>> 0) << shft) >>> 0;
for (let c = s >>> 3, clmt = sb.length; c < clmt | 0; c += bp) {
const isbit = ((sb[c] >>> shft) ^ 1) & 1;
cntarr[c >> 6] -= isbit; cullcnt += isbit; sb[c] |= msk; }
}
}
else
for (let slmt = sb.length << 3; s < slmt; s += bp) {
const sba = s >>> 3; const shft = s & 7;
const isbit = ((sb[sba] >>> shft) ^ 1) & 1;
cntarr[s >> 9] -= isbit; cullcnt += isbit;
sb[sba] |= ((1 >>> 0) << shft) >>> 0; }
return cullcnt;
}
// pre-culled pattern of small wheel primes...
const WHLPRMS = [ 2, 3, 5, 7, 11, 13, 17 ];
const WHLPTRNLEN = WHLPRMS.reduce((s, v) => s * v, 1) >>> 1; // odds only!
const WHLPTRN = function() { // larger than WHLPTRN by one buffer for overflow
const len = (WHLPTRNLEN + (PGSZBITS >>> 3) + 3) & (-4); // up 2 even 32 bits!
const arr = new Uint8Array(len);
for (let bp of WHLPRMS.slice(1)) partialSievePage(0, bp, arr);
arr[0] |= ~(-2 << ((WHLPRMS[WHLPRMS.length - 1] - 3) >> 1)) >>> 0; return arr;
}();
function fillPage(lwi, sb) {
const mod = (lwi / 8) % WHLPTRNLEN;
sb.set(new Uint8Array(WHLPTRN.buffer, mod, sb.length));
}
function cullPage(lwi, bpras, sb) {
const btsz = sb.length << 3; let bp = 3;
const nxti = lwi + btsz; // just beyond the current page
for (let bpra of bpras()) {
for (let bpri = 0; bpri < bpra.length; ++bpri) {
const bpr = bpra[bpri]; bp += bpr + bpr;
let s = (bp * bp - 3) / 2; // compute start index of prime squared
if (s >= nxti) return; // enough bp's
partialSievePage(lwi, bp, sb);
}
}
}
function soePages(bitsz, bpras) {
const buf = new Uint8Array(bitsz >> 3); let lowi = 0;
const gen = bpras === undefined ? makeBasePrimeRepArrs() : bpras;
return function*() {
while (true) {
fillPage(lowi, buf); cullPage(lowi, gen, buf);
yield { lwi: lowi, sb: buf }; lowi += bitsz; }
};
}
function makeBasePrimeRepArrs() {
const buf = new Uint8Array(128); let gen = undefined; // avoid data race!
fillPage(0, buf);
for (let i = 8, bp = 19, sqr = bp * bp; sqr < 2048+3;
++i, bp += 2, sqr = bp * bp)
if (((buf[i >> 3] >>> 0) & ((1 << (i & 7)) >>> 0)) === 0 >>> 0)
for (let c = (sqr - 3) >> 1; c < 1024; c += bp)
buf[c >> 3] |= (1 << (c & 7)) >>> 0; // init zeroth buf
function sb2bprs(sb) {
const btsz = sb.length << 3; let oi = 0;
const arr = new Uint8Array(countPageFromTo(0, btsz - 1, sb));
for (let i = 0, j = 0; i < btsz; ++i)
if (((sb[i >> 3] >>> 0) & ((1 << (i & 7)) >>> 0)) === 0 >>> 0) {
arr[j++] = (i - oi) >>> 0; oi = i; }
return { bpra: arr, lastgap: (btsz - oi) | 0 };
}
let { bpra, lastgap } = sb2bprs(buf);
function next() {
const nxtpg = sb2bprs(gen.next().value.sb);
nxtpg.bpra[0] += lastgap; lastgap = nxtpg.lastgap;
return { head: nxtpg.bpra, tail: lazy(next) };
}
const lazylist = { head: bpra, tail: lazy(function() {
if (gen === undefined) {
gen = soePages(1024)(); gen.next() } // past first page
return next();
}) };
return function*() { // return a generator of rep pages...
let ll = lazylist; while (true) { yield ll.head; ll = ll.tail(); }
};
}
function *revPrimesFrom(top, bpras) {
const topndx = (top - 3) >>> 1;
const buf = new Uint8Array(PGSZBITS >>> 3);
let lwi = (((topndx / PGSZBITS) >>> 0) * PGSZBITS) >>> 0;
let si = (topndx - lwi) >>> 0;
for (; lwi >= 0; lwi -= PGSZBITS) { // usually external limit!
const base = 3 + lwi + lwi;
fillPage(lwi, buf); cullPage(lwi, bpras, buf);
for (; si >= 0 >>> 0; --si)
if (((buf[si >> 3] >>> 0) & ((1 << (si & 7)) >>> 0)) === (0 >>> 0))
yield base + si + si;
si = PGSZBITS - 1;
}
};
const TinyPrimes = [ 2, 3, 5, 7, 11, 13, 17, 19 ]; // degree eight
const TinyPhiDegree = TinyPrimes.length;
const TinyProduct = TinyPrimes.reduce((s, v) => s * v) >>> 0;
const TinyHalfProduct = TinyProduct >>> 1;
const TinyTotient = TinyPrimes.reduce((s, v) => s * (v - 1), 1) >>> 0;
const TinyLength = (TinyProduct + 8) >>> 2; // include zero and half point!
const TinyTotients = function() {
const arr = new Uint32Array(TinyLength | 0);
arr[TinyLength - 1] = 1; // mark mid point value as not prime - never is
let spn = 3 * 5 * 7; arr[0] = 1; // mark zeroth value as not prime!
for (let bp of [ 3, 5, 7 ]) // cull small base prime values...
for (let c = (bp + 1) >>> 1; c <= spn; c += bp) arr[c] |= 1;
for (let bp of [ 11, 13, 17, 19 ]) {
for (let i = 1 + spn; i < TinyLength; i += spn) {
const rng = i + spn > TinyLength ? spn >> 1 : spn;
arr.set(new Uint32Array(arr.buffer, 4, rng), i); }
spn *= bp;
for (let c = (bp + 1) >>> 1; // eliminate prime in pattern!
c < (spn > TinyLength ? TinyLength : spn + 1); c += bp)
arr[c] |= 1;
}
arr.reduce((s, v, i) => { // accumulate sums...
const ns = s + (v ^ 1); arr[i] = ns; return ns; }, 0);
return arr;
}();
function tinyPhi(m) {
const d = Math.trunc(m / TinyProduct);
const ti = (m - d * TinyProduct + 1) >>> 1;
const t = ti < TinyLength
? TinyTotients[ti]
: TinyTotient - TinyTotients[TinyHalfProduct - ti];
return d * TinyTotient + t;
}
function *primeCountTo(limit) {
// if (limit > MAXVALUE) {
// console.error("Maximum integer size of", MAXVALUE, "exceeded!!!"); return; }
if (limit <= WHLPRMS[WHLPRMS.length - 1]) {
let cnt = 0; for (let p of WHLPRMS) { if (p > limit) break; else ++cnt; }
return cnt; }
const bpras = makeBasePrimeRepArrs();
if (limit < 1024**2 + 3) { // for limit < about a million, just sieve...
let p = 3; let cnt = WHLPRMS.length;
for (let bpra of bpras())
for (let bpr of bpra) { // just count base prime values to limit
p += bpr + bpr; if (p > limit) return cnt; ++cnt; }
}
if (limit <= 32 * 2 * PGSZBITS + 3) { // count sieve to about 32 million...
const lmti = (limit - 3) / 2;
let cnt = WHLPRMS.length; // just use page counting to limit as per usual...
for (let pg of soePages(PGSZBITS, bpras)()) {
const nxti = pg.lwi + (pg.sb.length << 3);
if (nxti > lmti) { cnt += countPageFromTo(0, lmti - pg.lwi, pg.sb); break; }
cnt += countPageFromTo(0, PGSZBITS - 1, pg.sb);
}
return cnt;
}
// Actual LMO prime counting code starts here...
const sqrt = Math.trunc(Math.sqrt(limit)) >>> 0;
const cbrt = Math.trunc(Math.cbrt(limit)) >>> 0;
const sqrtcbrt = Math.trunc(Math.sqrt(cbrt)) >>> 0;
const top = cbrt * cbrt - 1; // Math.trunc(limit / cbrt) - 1; // sized for maximum required!
const bsprms = function() {
let bp = 3; let cnt = WHLPRMS.length + 1; for (let bpra of bpras())
for (let bpr of bpra) {
bp += bpr + bpr; if (bp > cbrt) return new Uint32Array(cnt); ++cnt; }
}();
bsprms.set(WHLPRMS, 1); // index zero not used == 0!
const pisqrtcbrt = function() {
let cnt = WHLPRMS.length; let i = cnt + 1; let bp = 3;
stop: for (let bpra of bpras())
for (let bpr of bpra) {
bp += bpr + bpr; if (bp > cbrt) break stop;
if (bp <= sqrtcbrt) ++cnt; bsprms[i++] = bp >>> 0; }
return cnt;
}();
const pis = function() { // starts with index 0!
const arr = new Uint32Array(cbrt + 2); let j = 0;
for (let i = 1; i < bsprms.length; ++i)
for (; j < bsprms[i]; ) arr[j++] = (i - 1) >>> 0;
for (; j < arr.length; ) arr[j++] = (bsprms.length - 1) >>> 0;
return arr;
}();
const phis = function() { // index below TinyPhi degree never used...
const arr = (new Array(bsprms.length)).fill(1);
arr[0] = 0; arr[1] = 3; arr[2] = 2; // unused
for (let i = WHLPRMS.length + 2; i < arr.length; ++i) {
arr[i] -= i - WHLPRMS.length - 1; } // account for non phi primes!
return arr;
}();
// indexed by `m`, contains greatest prime factor and
// Moebius value bit; Moebius one is negative...
const specialroots = new Uint16Array(cbrt + 1); // filled in with S1 below...
const S1 = function() { // it is very easy to calculate S1 recursively...
let s1acc = tinyPhi(limit);
function level(lmtlpfni, mfv, m) {
for (let lpfni = 9; lpfni < lmtlpfni; ++lpfni) {
const pn = bsprms[lpfni]; const nm = m * pn;
if (nm > cbrt) { // don't split, found S2 root leaf...
specialroots[m] = (lmtlpfni << 1) | (mfv < 0 ? 1 : 0); return; }
else { // recurse for S1; never more than 11 levels deep...
s1acc += mfv * tinyPhi(Math.trunc(limit / nm)); // down level...
level(lpfni, -mfv, nm); // Moebius sign change on down level!
} // up prime value, same level!
}
}
level(bsprms.length, -1, 1); return s1acc;
}();
// at last, calculate the more complex parts of the final answer:
function *complex() {
let s2acc = 0; let p2acc = 0; let p2cnt = 0; // for "P2" calculation
const buf = new Uint8Array(PGSZBITS >>> 3); let ttlcnt = 0;
const cnts = new Uint8Array(PGSZBITS >>> 9);
const cntaccs = new Uint32Array(cnts.length);
const revgen = revPrimesFrom(sqrt, bpras);
let p2v = Math.trunc(limit / revgen.next().value);
const lwilmt = Math.trunc((top - 3) / 2);
for (let lwi = 0; lwi <= lwilmt; lwi += PGSZBITS) { // for all pages
if ((lwi & 63) == 0) yield lwi / lwilmt * 100.0;
let pgcnt = 0; const low = 3 + lwi + lwi;
const high = Math.min(low + (PGSZBITS << 1) - 1, top);
let cntstrti = 0 >>> 0;
function countTo(stop) {
const cntwrd = stop >>> 9; const bsndx = stop & (-512);
const xtr = countPageFromTo(bsndx, stop, buf);
while (cntstrti < cntwrd) {
const ncnt = cntaccs[cntstrti] + cnts[cntstrti];
cntaccs[++cntstrti] = ncnt; }
return cntaccs[cntwrd] + xtr;
}
const bpilmt = pis[Math.trunc(Math.sqrt(high)) >>> 0] >>> 0;
const maxbpi = pis[Math.min(cbrt, Math.trunc(Math.sqrt(limit/low)))]>>>0;
const tminbpi = pis[Math.min(Math.trunc(top / (high + 1)),
bsprms[maxbpi]) >>> 0];
const minbpi = Math.max(TinyPrimes.length, tminbpi) + 1;
fillPage(lwi, buf); let bpi = (WHLPRMS.length + 1) >>> 0;
if (minbpi <= maxbpi) { // jump to doing P2 if not range
// for bpi < minbpi there are no special leaves...
for (; bpi < minbpi; ++bpi) { // eliminate all Tiny Phi primes...
const bp = bsprms[bpi]; const i = (bp - 3) >>> 1; // cull base primes!
phis[bpi] += countPageFromTo(0, PGSZBITS - 1, buf);
partialSievePage(lwi, bp, buf); }
for (let i = 0; i < cnts.length; ++i) { // init cnts arr...
const s = i << 9; const c = countPageFromTo(s, s + 511, buf);
cnts[i] = c; pgcnt += c; }
// for all base prime values up to limit**(1/6) in the page,
// add all special leaves composed of this base prime value and
// any number of other higher base primes, all different,
// that qualify as special leaves...
let brkchkr = false;
for (; bpi <= Math.min(pisqrtcbrt, maxbpi) >>> 0; ++bpi) {
const bp = bsprms[bpi];
const minm = Math.max(Math.trunc(limit / (bp * (high + 1))),
Math.trunc(cbrt / bp)) >>> 0;
const maxm = Math.min(Math.trunc(limit / (bp * low)), cbrt) >>> 0;
if (bp >= maxm) { brkchkr = true; break; }
for (let m = maxm; m > minm; --m) {
const rt = specialroots[m];
if (rt != 0 && bpi < rt >>> 1) {
const stop = Math.trunc(limit / (bp * m) - low) >>> 1;
const mu = ((rt & 1) << 1) - 1; // one bit means negative!
s2acc -= mu * (phis[bpi] + countTo(stop));
} }
phis[bpi] += pgcnt; // update intermediate base prime counters
pgcnt -= partialSieveCountPage(lwi, bp, cnts, buf);
cntstrti = 0; cntaccs[0] = 0;
}
// for all base prime values > limit**(1/6) in the page,
// add results of all special leaves composed using only two primes...
if (!brkchkr)
for (; bpi <= maxbpi; ++bpi) {
const bp = bsprms[bpi];
let l = pis[Math.min(Math.trunc(limit / (bp * low)), cbrt)>>>0]>>>0;
if (bp >= bsprms[l]) break;
const piminm = pis[Math.max(Math.trunc(limit / (bp * (high + 1))),
bp) >>> 0] >>> 0;
for (; l > piminm; --l) {
const stop = Math.trunc(limit / (bp * bsprms[l]) - low) >>> 1;
s2acc += phis[bpi] + countTo(stop);
}
phis[bpi] += pgcnt; // update intermediate base prime counters
if (bpi <= bpilmt) {
pgcnt -= partialSieveCountPage(lwi, bp, cnts, buf);
cntstrti = 0; cntaccs[0] = 0; }
}
}
// complete cull page segment, then count up "P2" terms in range...
for (; bpi <= bpilmt; ++bpi) partialSievePage(lwi, bsprms[bpi], buf);
let ndx = 0 >>> 0;
while (p2v >= low && p2v <= high) {
const nndx = (p2v - low) >>> 1;
++p2cnt; ttlcnt += countPageFromTo(ndx, nndx, buf);
p2acc += ttlcnt;
ndx = (nndx + 1) >>> 0; p2v = Math.trunc(limit / revgen.next().value);
}
if (ndx < PGSZBITS) ttlcnt += countPageFromTo(ndx, PGSZBITS - 1, buf);
}
const Piy = bsprms.length - 1;
// adjust for now known delta picbrt to pisqrt!
p2acc -= p2cnt * ((p2cnt - 1) / 2 + (Piy - WHLPRMS.length));
// console.log("S1: ", S1);
// console.log("S2: ", s2acc);
// console.log("P2: ", p2acc);
// console.log("Piy", Piy);
// console.log("p2cnt: ", p2cnt);
yield S1 + s2acc - p2acc + Piy - 1;
}
yield* complex();
}
const limit = 1e11; // sieve to this limit...
function last(gen) {
let lst;
for (let r = gen.next(); !r.done; lst = r, r = gen.next()) ;
return lst.value;
}
const start = Date.now();
const answr = last(primeCountTo(limit));
const elpsd = Date.now() - start;
console.log("The number of primes to", limit, "is", answr, "in", elpsd, "milliseconds."); Note that the code has the capability of outputting a progress indication if one cares to hook it up... |
Beta Was this translation helpful? Give feedback.
-
ChatGPT is fun , it tried to code LMO for me.
|
Beta Was this translation helpful? Give feedback.
-
Since Stack overflow frowns on extended dialogs in comments sections, especially when moving outside the subject of the question, I'll open a discussion port in your actual project here.
Your goal of making a web page to do what the nth prime page does with a server is quite a good one, but I wouldn't use JavaScript for this, but would likely use Fable/F# to generate the JavaScript, so won't contribute to your code, but can give you some advice and pointers.
I hope you realize that the page-segmented optimizations I made on the SO thread also apply here as to count the primes to 10^15, you need to sieve to 10^10, which will take about five seconds with my techniques but minutes conventionally. However, that is not your current bottleneck, which is the huge number of integer divisions due to the Meissel-Lehmer implementation at something like 50 CPU clock cycles each. This doesn't matter much with the optimizations that Lehmer used and for his smallish ranges, but is a huge overhead as the ranges get larger.
In contrast, modern algorithms use a partial sieving technique that requires almost no integer divisions. Also, the Lagarias-Miller-Odlyzko (LMO) algorithm greatly reduces the number of operations so one gets about theoretical empirical orders of growth in execution time: if it takes one second to compute to 10^12, it will take about a hundred seconds to compute to 10^15.
LMO is a little more complex to use than Meissel-Lehmer but not that much more, especially in its first basic form. The most modern techniques are about ten times faster than LMO, but a lot more complex and probably not worth it to you if one can get your time to count the primes to the 53-bit number range down to minutes; once you have LMO working, you can look into these as a way of reducing maximum computation time to a number of seconds.
Beta Was this translation helpful? Give feedback.
All reactions