codelab/pi.cc (120 lines of code) (raw):
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cassert>
#include <charconv>
#include <cmath>
#include <cstdlib>
#include <future>
#include <iostream>
#include <string_view>
#include <system_error>
#include <thread>
#include <utility>
#include <tuple>
#include <fmt/core.h>
#include <gmp.h>
#include <gmpxx.h>
#include <mpfr.h>
namespace {
constexpr unsigned int PREC_MIN = 1, PREC_MAX = 100000000;
constexpr unsigned int PREC_EXTRA_BITS = 16;
constexpr long A = 13591409, B = 545140134, C = 640320, D = 12;
constexpr long C3_OVER_24 = C * C * C / 24;
struct mpfr_real {
mpfr_t v;
mpfr_real() = delete;
mpfr_real(const mpfr_real&) = delete;
mpfr_real(const mpf_class &f) {
mpfr_init2(v, f.get_prec());
mpfr_set_f(v, f.get_mpf_t(), MPFR_RNDN);
}
~mpfr_real() { mpfr_clear(v); }
mpfr_ptr ptr() { return v; }
};
using bs_return_type = std::tuple<mpz_class, mpz_class, mpz_class>;
bs_return_type bs(const long a, const long b, const int depth);
mpfr_real chudnovsky_pi(const long prec, const long prec_bits);
struct bs_invoker {
const int max_depth;
bs_invoker() : max_depth(std::log2(std::thread::hardware_concurrency())) {}
bs_invoker(const bs_invoker&) = delete;
bs_invoker(int max_depth) : max_depth(max_depth) {}
auto operator()(const long a, const long b, int depth) const {
if (depth > max_depth) {
std::promise<bs_return_type> promise;
promise.set_value(bs(a, b, depth));
return promise.get_future();
}
return std::async(std::launch::async, bs, a, b, depth);
};
};
bs_return_type bs(const long a, const long b, const int depth) {
mpz_class p, q, t;
static bs_invoker invoker;
if (b - a == 1) {
if (a == 0) {
p = 1;
q = 1;
} else {
p = (6 * a - 5);
p *= (2 * a - 1);
p *= (6 * a - 1);
q = a;
q = q * a * a * C3_OVER_24;
}
t = p * (A + mpz_class(B) * a);
if (a % 2)
t = -t;
} else {
auto m = (a + b) / 2;
auto f1 = invoker(a, m, depth + 1);
auto f2 = invoker(m, b, depth + 1);
auto [p1, q1, t1] = f1.get();
auto [p2, q2, t2] = f2.get();
p = p1 * p2;
q = q1 * q2;
t = t1 * q2 + p1 * t2;
}
return {p, q, t};
}
mpfr_real chudnovsky_pi(const long prec, const long prec_bits) {
const double digits_per_term =
std::log10(static_cast<double>(C3_OVER_24) / 6 / 2 / 6);
const long terms = prec / digits_per_term + 2;
fmt::print(stderr, "Number of terms = {}, digits per term = {}\n", terms,
digits_per_term);
auto [p, q, t] = bs(0, terms, 0);
std::cerr << "Summation series complete. Final steps..." << std::endl;
mpf_class q1(std::move(q), prec_bits);
q1 = q1 * (C / D) / t;
mpf_class sqrtC(sqrt(mpf_class(C, prec_bits)));
return mpf_class(q1 * sqrtC);
}
void print_usage() {
fmt::print(R"EOS(Usage: ./pi <number of digits>
Number of digits must be [{}, {}].
)EOS",
PREC_MIN, PREC_MAX);
}
} // namespace
int main(int argc, char *argv[]) {
unsigned int prec = 100;
if (argc != 2) {
print_usage();
return 1;
}
const std::string_view arg(argv[1]);
auto [ptr, ec] = std::from_chars(arg.begin(), arg.end(), prec);
if (ptr != arg.end() && ec != std::errc()) {
print_usage();
return 1;
}
if (prec < PREC_MIN || PREC_MAX < prec) {
print_usage();
return 1;
}
const unsigned long prec_bits = prec * std::log2(10.) + PREC_EXTRA_BITS;
fmt::print(stderr, "Calculating {} digits of pi...\n", prec);
fmt::print(stderr, "Internal precision = {} bits\n", prec_bits);
auto pi = chudnovsky_pi(prec, prec_bits);
// Use MPFR to round to -inf.
mpfr_printf("%.*RDf\n", (mpfr_prec_t)prec, pi.ptr());
return 0;
}