Compare commits

...

4 Commits
v1.1 ... main

Author SHA1 Message Date
A.M. Rowsell ec2a96b0ad
Very small changes
Considering ways to take the algorithm and turn it into
OpenCL/Boost::Compute functions/code which would offer
a large speed increase.

Also, if I can figure out how to start from any arbitrary
digit (which is supposed to be possible) then multi-threading
would also give great speedups.
2023-10-08 07:29:55 -04:00
A.M. Rowsell 7b3fc5cdf8
Added a newline at the end of output 2023-06-26 00:22:24 -04:00
A.M. Rowsell aac6d97593
Added a readme, finally 2023-06-25 08:40:49 -04:00
A.M. Rowsell bc12d981d0
Link Windows exe statically, change location of std::flush
The Windows exe has to be linked statically, because Windows
is stupid and doesn't have dynamic libraries like the stdc++ lib
available. Maybe it's in some .dll somewhere, but I can't be
bothered. Linux is just better.

The change to the location of flush will make the output look
more "spigot"-like, ie with large numbers of digits generated,
the digits will be output in small groups instead of a nice
smooth digit at a time. For some reason this is how most
spigot algorithms do it, and so to me it "looks" correct. It
doesn't actually change anything whatsoever other than when
the standard output actually gets printed to console.
2023-06-20 17:55:09 -04:00
5 changed files with 30 additions and 3 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
cfg/
*.txt
main
.gdb_history

View File

@ -2,7 +2,7 @@ CC=x86_64-w64-mingw32-gcc
CXX=x86_64-w64-mingw32-g++
RM=rm -f
CPPFLAGS=-Wall
LDFLAGS=
LDFLAGS=-static
LDLIBS=-lm
SRCS=main.cpp Spigot.cpp

11
README.md Normal file
View File

@ -0,0 +1,11 @@
# Pi Spigot in C++
## What is spigot?
Spigot is an algorithm that calculates digits of pi. Each time you "pump" the spigot, you will get some number of digits out. Depending on the internal state of the algorithm, you might get no digits, 1 digit (most of the time), or 2+ digits. So in order to get x number of digits, you pump the spigot x number of times, and you should generally get that many digits out. The algorithm uses a vector of ints, sized to n * 10/3 where n is the number of requested digits. So for 10000 digits out, the vector will be 33333 digits long. This is to ensure there are enough digits to maintain precision.
Spigot is very fast for small values of n, but as n gets bigger, it slows down considerably. For example, calculating a million digits took about 18 hours, but 50 million digits had only produced just over 50k digits in that same amount of time. This is because the vector gets so much longer -- compare 3.3 million versus 166 million -- and each "pump" of the spigot requires accessing every single digit of the vector from n to the end. Because of this, spigot also speeds up as it gets closer to reaching its target, meaning predicting how long it will take to complete is non-obvious.
## Why Spigot?
The Chudnovsky algorithm is much more efficient, although also more complicated to implement. The Spigot algorithm does scale infinitely, and is simple to implement, so for calculating digits of Pi it used to be a great choice. But now that tools like y-cruncher exist which can calculate literally trillions of digits in the span of weeks on supercomputers exist, spigot is not used much anymore. But I think it's a cool algorithm, as it uses integers and integer division to calculate a transcendental number! It's also a good algorithm to practice a new language with. There are quite a few little gotchas that differ from language to language, and implementing an algorithm like spigot is a good way to learn common mistakes in a language. I have written this version in C++, and before this I also wrote a version in Python. The speedup from moving to C++ is simply astounding, and with gcc using full optimization, the algorithm can rip through even many tens of thousands of digits very quickly.

View File

@ -15,6 +15,12 @@ void Spigot::pump(void) {
int tempPreDigit = 0;
int j = this->spigotListLength - 1;
int i = j;
/*
* This could be handled using boost::compute
* with a BOOST_COMPUTE_FUNCTION that just
* multiplies every item by 10.
*/
while(i >= 0) {
this->spigotList[i] *= 10;
i--;
@ -22,6 +28,13 @@ void Spigot::pump(void) {
this->carry = 0;
i = j;
// note this does *not* handle the i=0 case
/*
* This might also be able to be sped up
* with OpenCL/boost::compute by creating
* another custom function, but because
* the carry has to be passed along this might
* not work well
*/
while(i > 0) {
this->spigotList[i] += this->carry;
this->carry = (this->spigotList[i] / (i * 2 + 1)) * i;
@ -39,7 +52,7 @@ void Spigot::pump(void) {
// output all predigits
long unsigned pdLen = this->preDigits.size();
for(long unsigned int j = 0; j < pdLen; j++) {
std::cout << this->preDigits.back() << std::flush;
std::cout << this->preDigits.back();
this->preDigits.pop_back();
}
this->preDigits.insert(this->preDigits.begin(), tempPreDigit);
@ -50,7 +63,7 @@ void Spigot::pump(void) {
tempPreDigit = 0;
long unsigned pdLen = this->preDigits.size();
for(long unsigned int j = 0; j < pdLen; j++) {
std::cout << (this->preDigits.back() + 1) % 10 << std::flush;
std::cout << (this->preDigits.back() + 1) % 10;
this->preDigits.pop_back();
}
this->preDigits.insert(this->preDigits.begin(), tempPreDigit);
@ -60,5 +73,6 @@ void Spigot::pump(void) {
} catch (int e) {
std::cout << "An exception " << e << " occurred." << std::endl;
}
std::cout << std::flush;
return;
}

View File

@ -14,5 +14,6 @@ int main(int argc, char **argv) {
for(int i = 0; i < numDigits; i++) {
piSpigot->pump();
}
std::cout << std::endl;
return 0;
}