/////////////////////////////////////////////////////////////////// // Copyright Christopher Kormanyos 2020 - 2022. // // Distributed under the Boost Software License, // // Version 1.0. (See accompanying file LICENSE_1_0.txt // // or copy at http://www.boost.org/LICENSE_1_0.txt) // /////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #define EXAMPLE008_BERNOULLI_USE_LOCAL_PI #if !defined(BOOST_MP_STANDALONE) #define BOOST_MP_STANDALONE #endif #if !defined(EXAMPLE008_BERNOULLI_USE_LOCAL_PI) #if !defined(BOOST_MATH_STANDALONE) #define BOOST_MATH_STANDALONE #endif #include #endif #include // cd /mnt/c/Users/User/Documents/Ks/PC_Software/Test // g++ -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wundef -O3 -std=c++11 -I/mnt/c/boost/modular_boost/boost/libs/multiprecision/include -I/mnt/c/boost/modular_boost/boost/libs/config/include standalone_bernoulli_tgamma.cpp -o standalone_bernoulli_tgamma.exe // D:\MinGW_nuwen\MinGW\bin\g++ -Wall -Wextra -Wconversion -Wsign-conversion -Wshadow -Wundef -O3 -std=c++11 -IC:\boost\modular_boost\boost\libs\multiprecision\include -IC:\boost\modular_boost\boost\libs\config\include test.cpp -o test.exe namespace example008_bernoulli { constexpr std::int32_t wide_decimal_digits10 = INT32_C(1001); using wide_float_backend_type = boost::multiprecision::cpp_bin_float>; using wide_float_type = boost::multiprecision::number; template auto pi() -> FloatingPointType { return static_cast(3.1415926535897932384626433832795029L); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) } #if defined(EXAMPLE008_BERNOULLI_USE_LOCAL_PI) template auto calc_pi() -> FloatingPointType { // Use a quadratically convergent Gauss AGM to compute pi. using floating_point_type = FloatingPointType; floating_point_type val_pi; floating_point_type a(1U); // Initialize bB to 0.5. floating_point_type bB(0.5F); // NOLINT(readability-identifier-naming,cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) // Initialize t to 0.375. floating_point_type t(static_cast(3U) / 8U); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) floating_point_type s(bB); // This loop is designed for a maximum of several million // decimal digits of pi. The index k should reach no higher // than about 25 or 30. After about 20 iterations, the precision // is about one million decimal digits. const auto digits10_iteration_goal = static_cast ( static_cast(std::numeric_limits::digits10 / 2) + static_cast(9U) ); using std::log; using std::lround; const auto digits10_scale = static_cast ( lround ( static_cast(1000.0F * log(static_cast(std::numeric_limits::radix))) / log(10.0F) ) ); for(auto k = static_cast(UINT8_C(0)); k < static_cast(UINT8_C(48)); ++k) { using std::sqrt; a += sqrt(bB); a /= 2U; val_pi = a; val_pi *= a; bB = val_pi; bB -= t; bB *= 2U; floating_point_type iterate_term(bB); iterate_term -= val_pi; iterate_term *= static_cast(1ULL << (k + 1U)); // NOLINT(google-runtime-int) s += iterate_term; // Test the number of precise digits from this iteration. // If it is there are enough precise digits, then the calculation // is finished. const auto ib = (std::max) ( static_cast(0), static_cast(-ilogb(iterate_term)) ); const auto digits10_of_iteration = static_cast ( static_cast(static_cast(ib) * digits10_scale) / UINT32_C(1000) ); // Estimate the approximate decimal digits of this iteration term. // If we have attained about half of the total desired digits // with this iteration term, then the calculation is finished // because the change from the next iteration will be // insignificantly small. if(digits10_of_iteration > digits10_iteration_goal) { break; } t = val_pi; t += bB; t /= 4U; } return (val_pi + bB) / s; } template auto my_pi() -> const FloatingPointType& { using floating_point_type = FloatingPointType; static const floating_point_type local_pi = calc_pi(); return local_pi; } #endif template<> auto pi() -> wide_float_type { #if defined(EXAMPLE008_BERNOULLI_USE_LOCAL_PI) return my_pi(); #else return boost::math::constants::pi(); #endif } auto bernoulli_table() -> std::vector& { static std::vector bernoulli_table ( static_cast ( static_cast(std::numeric_limits::digits10) * 0.95F ) ); return bernoulli_table; } template auto bernoulli_b(FloatingPointType* bn, std::uint32_t n) -> void { using floating_point_type = FloatingPointType; // See reference "Computing Bernoulli and Tangent Numbers", Richard P. Brent. // See also the book Richard P. Brent and Paul Zimmermann, "Modern Computer Arithmetic", // Cambridge University Press, 2010, p. 237. const auto m = static_cast(n / 2U); std::vector tangent_numbers(m + 1U); tangent_numbers[0U] = 0U; tangent_numbers[1U] = 1U; for(std::uint32_t k = 1U; k < m; ++k) { tangent_numbers[k + 1U] = tangent_numbers[k] * k; } for(auto k = static_cast(2U); k <= m; ++k) { for(auto j = k; j <= m; ++j) { const std::uint32_t j_minus_k = j - k; tangent_numbers[j] = (tangent_numbers[j - 1] * j_minus_k) + (tangent_numbers[j] * (j_minus_k + 2U)); } } floating_point_type two_pow_two_m(4U); for(std::uint32_t i = 1U; i < static_cast(n / 2U); ++i) { const auto two_i = static_cast(2U * i); const floating_point_type b = (tangent_numbers[i] * two_i) / (two_pow_two_m * (two_pow_two_m - 1)); const bool b_neg = ((two_i % 4U) == 0U); bn[two_i] = ((!b_neg) ? b : -b); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) two_pow_two_m *= 4U; } bn[0U] = 1U; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) bn[1U] = floating_point_type(-1) / 2U; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) } template auto tgamma(const FloatingPointType& x) -> FloatingPointType { using floating_point_type = FloatingPointType; // Check if the argument should be scaled up for the Bernoulli series expansion. static const auto min_arg_n = static_cast ( static_cast(static_cast(std::numeric_limits::digits10) * 0.8F) ); static const floating_point_type min_arg_x = floating_point_type(min_arg_n); const auto n_recur = static_cast ( (x < min_arg_x) ? static_cast(static_cast(min_arg_n - static_cast(x)) + 1U) : static_cast(0U) ); floating_point_type xx(x); // Scale the argument up and use downward recursion later for the final result. if(n_recur != 0U) { xx += n_recur; } floating_point_type one_over_x_pow_two_n_minus_one = 1 / xx; const floating_point_type one_over_x2 = one_over_x_pow_two_n_minus_one * one_over_x_pow_two_n_minus_one; floating_point_type sum = (one_over_x_pow_two_n_minus_one * bernoulli_table()[2U]) / 2U; floating_point_type tol = std::numeric_limits::epsilon(); using std::log; if(xx > 8U) // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) { // In the following code sequence, we extract the approximate logarithm // of the argument x and use the leading term of Stirling's approximation, // which is Log[Gamma[x]] aprox. (x (Log[x] - 1)) in order to scale // the tolerance. In order to do this, we find the built-in floating point // approximation of (x (Log[x] - 1)). // Limit fx to the range 8 <= fx <= 10^16, where 8 is chosen to // ensure that (log(fx) - 1.0F) remains positive and 10^16 is // selected arbitrarily, yet ensured to be rather large. auto fx_max = (std::max)(static_cast(8U), xx); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) auto fx = (std::min)(fx_max, static_cast(UINT64_C(10000000000000000))); tol *= static_cast(fx * (log(fx) - 1.0F)); } // Perform the Bernoulli series expansion. for(auto n2 = static_cast(4U); n2 < static_cast(bernoulli_table().size()); n2 += 2U) { one_over_x_pow_two_n_minus_one *= one_over_x2; const floating_point_type term = (one_over_x_pow_two_n_minus_one * bernoulli_table()[n2]) / static_cast(static_cast(n2) * static_cast(n2 - 1U)); using std::fabs; if((n2 > 6U) && (fabs(term) < tol)) // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) { break; } sum += term; } using example008_bernoulli::pi; using std::exp; static const floating_point_type half = floating_point_type(1U) / 2U; static const floating_point_type half_ln_two_pi = log(pi() * 2U) / 2U; floating_point_type g = exp(((((xx - half) * log(xx)) - xx) + half_ln_two_pi) + sum); // Rescale the result using downward recursion if necessary. for(auto k = static_cast(0U); k < n_recur; ++k) { g /= --xx; } return g; } } // namespace example008_bernoulli auto example008_bernoulli_tgamma() -> bool { const std::clock_t start = std::clock(); // Initialize the table of Bernoulli numbers. example008_bernoulli::bernoulli_b ( example008_bernoulli::bernoulli_table().data(), static_cast(example008_bernoulli::bernoulli_table().size()) ); // In this example, we compute values of Gamma[1/2 + n]. // We will make use of the relation // (2n)! // Gamma[1/2 + n] = -------- * Sqrt[Pi]. // (4^n) n! // Table[Factorial[2 n]/((4^n) Factorial[n]), {n, 0, 17, 1}] const std::array, 18U> ratios = {{ { UINT64_C( 1), UINT32_C( 1) }, { UINT64_C( 1), UINT32_C( 2) }, { UINT64_C( 3), UINT32_C( 4) }, { UINT64_C( 15), UINT32_C( 8) }, { UINT64_C( 105), UINT32_C( 16) }, { UINT64_C( 945), UINT32_C( 32) }, { UINT64_C( 10395), UINT32_C( 64) }, { UINT64_C( 135135), UINT32_C( 128) }, { UINT64_C( 2027025), UINT32_C( 256) }, { UINT64_C( 34459425), UINT32_C( 512) }, { UINT64_C( 654729075), UINT32_C( 1024) }, { UINT64_C( 13749310575), UINT32_C( 2048) }, { UINT64_C( 316234143225), UINT32_C( 4096) }, { UINT64_C( 7905853580625), UINT32_C( 8192) }, { UINT64_C( 213458046676875), UINT32_C( 16384) }, { UINT64_C( 6190283353629375), UINT32_C( 32768) }, { UINT64_C( 191898783962510625), UINT32_C( 65536) }, { UINT64_C(6332659870762850625), UINT32_C(131072) } }}; bool result_is_ok = true; using example008_bernoulli::wide_float_type; const wide_float_type tol (std::numeric_limits::epsilon() * UINT32_C(100000)); const wide_float_type half(0.5F); for(auto i = static_cast(0U); i < ratios.size(); ++i) { // Calculate Gamma[1/2 + i] const wide_float_type g = example008_bernoulli::tgamma(half + i); // Calculate the control value. using example008_bernoulli::pi; using std::fabs; using std::sqrt; const wide_float_type control = (sqrt(pi()) * ratios[i].first) / ratios[i].second; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) const wide_float_type closeness = fabs(1 - (g / control)); result_is_ok &= (closeness < tol); } const std::clock_t stop = std::clock(); std::cout << "Time example008_bernoulli_tgamma(): " << static_cast(stop - start) / static_cast(CLOCKS_PER_SEC) << std::endl; return result_is_ok; } int main() { const bool result_is_ok = example008_bernoulli_tgamma(); std::cout << "result_is_ok: " << std::boolalpha << result_is_ok << std::endl; }