/////////////////////////////////////////////////////////////////////////////// // Copyright 2013 John Maddock // 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) #ifndef BOOST_MATH_BERNOULLI_DETAIL_HPP #define BOOST_MATH_BERNOULLI_DETAIL_HPP #include #include #include #include #include #include #include #include #if defined(BOOST_MATH_HAS_THREADS) && !defined(BOOST_NO_CXX11_HDR_MUTEX) && !defined(BOOST_MATH_NO_ATOMIC_INT) #include #else # define BOOST_MATH_BERNOULLI_NOTHREADS #endif namespace boost{ namespace math{ namespace detail{ // // Asymptotic expansion for B2n due to // Luschny LogB3 formula (http://www.luschny.de/math/primes/bernincl.html) // template T b2n_asymptotic(int n) { BOOST_MATH_STD_USING const auto nx = static_cast(n); const T nx2(nx * nx); const T approximate_log_of_bernoulli_bn = ((boost::math::constants::half() + nx) * log(nx)) + ((boost::math::constants::half() - nx) * log(boost::math::constants::pi())) + (((T(3) / 2) - nx) * boost::math::constants::ln_two()) + ((nx * (T(2) - (nx2 * 7) * (1 + ((nx2 * 30) * ((nx2 * 12) - 1))))) / (((nx2 * nx2) * nx2) * 2520)); return ((n / 2) & 1 ? 1 : -1) * (approximate_log_of_bernoulli_bn > tools::log_max_value() ? policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, nx, Policy()) : static_cast(exp(approximate_log_of_bernoulli_bn))); } template T t2n_asymptotic(int n) { BOOST_MATH_STD_USING // Just get B2n and convert to a Tangent number: T t2n = fabs(b2n_asymptotic(2 * n)) / (2 * n); T p2 = ldexp(T(1), n); if(tools::max_value() / p2 < t2n) { return policies::raise_overflow_error("boost::math::tangent_t2n<%1%>(std::size_t)", nullptr, T(n), Policy()); } t2n *= p2; p2 -= 1; if(tools::max_value() / p2 < t2n) { return policies::raise_overflow_error("boost::math::tangent_t2n<%1%>(std::size_t)", nullptr, Policy()); } t2n *= p2; return t2n; } // // We need to know the approximate value of /n/ which will // cause bernoulli_b2n(n) to return infinity - this allows // us to elude a great deal of runtime checking for values below // n, and only perform the full overflow checks when we know that we're // getting close to the point where our calculations will overflow. // We use Luschny's LogB3 formula (http://www.luschny.de/math/primes/bernincl.html) // to find the limit, and since we're dealing with the log of the Bernoulli numbers // we need only perform the calculation at double precision and not with T // (which may be a multiprecision type). The limit returned is within 1 of the true // limit for all the types tested. Note that although the code below is basically // the same as b2n_asymptotic above, it has been recast as a continuous real-valued // function as this makes the root finding go smoother/faster. It also omits the // sign of the Bernoulli number. // struct max_bernoulli_root_functor { explicit max_bernoulli_root_functor(unsigned long long t) : target(static_cast(t)) {} double operator()(double n) const { BOOST_MATH_STD_USING // Luschny LogB3(n) formula. const double nx2(n * n); const double approximate_log_of_bernoulli_bn = ((boost::math::constants::half() + n) * log(n)) + ((boost::math::constants::half() - n) * log(boost::math::constants::pi())) + (((static_cast(3) / 2) - n) * boost::math::constants::ln_two()) + ((n * (2 - (nx2 * 7) * (1 + ((nx2 * 30) * ((nx2 * 12) - 1))))) / (((nx2 * nx2) * nx2) * 2520)); return approximate_log_of_bernoulli_bn - target; } private: double target; }; template inline std::size_t find_bernoulli_overflow_limit(const std::false_type&) { // Set a limit on how large the result can ever be: static const auto max_result = static_cast((std::numeric_limits::max)() - 1000u); unsigned long long t = lltrunc(boost::math::tools::log_max_value()); max_bernoulli_root_functor fun(t); boost::math::tools::equal_floor tol; std::uintmax_t max_iter = boost::math::policies::get_max_root_iterations(); double result = boost::math::tools::toms748_solve(fun, sqrt(static_cast(t)), static_cast(t), tol, max_iter).first / 2; if (result > max_result) { result = max_result; } return static_cast(result); } template inline std::size_t find_bernoulli_overflow_limit(const std::true_type&) { return max_bernoulli_index::value>::value; } template std::size_t b2n_overflow_limit() { // This routine is called at program startup if it's called at all: // that guarantees safe initialization of the static variable. using tag_type = std::integral_constant::value >= 1) && (bernoulli_imp_variant::value <= 3)>; static const std::size_t lim = find_bernoulli_overflow_limit(tag_type()); return lim; } // // The tangent numbers grow larger much more rapidly than the Bernoulli numbers do.... // so to compute the Bernoulli numbers from the tangent numbers, we need to avoid spurious // overflow in the calculation, we can do this by scaling all the tangent number by some scale factor: // template ::is_specialized && (std::numeric_limits::radix == 2), bool>::type = true> inline T tangent_scale_factor() { BOOST_MATH_STD_USING return ldexp(T(1), std::numeric_limits::min_exponent + 5); } template ::is_specialized || !(std::numeric_limits::radix == 2), bool>::type = true> inline T tangent_scale_factor() { return tools::min_value() * 16; } // // We need something to act as a cache for our calculated Bernoulli numbers. In order to // ensure both fast access and thread safety, we need a stable table which may be extended // in size, but which never reallocates: that way values already calculated may be accessed // concurrently with another thread extending the table with new values. // // Very very simple vector class that will never allocate more than once, we could use // boost::container::static_vector here, but that allocates on the stack, which may well // cause issues for the amount of memory we want in the extreme case... // template struct fixed_vector : private std::allocator { using size_type = unsigned; using iterator = T*; using const_iterator = const T*; fixed_vector() : m_used(0) { std::size_t overflow_limit = 5 + b2n_overflow_limit >(); m_capacity = static_cast((std::min)(overflow_limit, static_cast(100000u))); m_data = this->allocate(m_capacity); } ~fixed_vector() { using allocator_type = std::allocator; using allocator_traits = std::allocator_traits; allocator_type& alloc = *this; for(unsigned i = 0; i < m_used; ++i) { allocator_traits::destroy(alloc, &m_data[i]); } allocator_traits::deallocate(alloc, m_data, m_capacity); } T& operator[](unsigned n) { BOOST_MATH_ASSERT(n < m_used); return m_data[n]; } const T& operator[](unsigned n)const { BOOST_MATH_ASSERT(n < m_used); return m_data[n]; } unsigned size()const { return m_used; } unsigned size() { return m_used; } bool resize(unsigned n, const T& val) { if(n > m_capacity) { #ifndef BOOST_MATH_NO_EXCEPTIONS BOOST_MATH_THROW_EXCEPTION(std::runtime_error("Exhausted storage for Bernoulli numbers.")); #else return false; #endif } for(unsigned i = m_used; i < n; ++i) new (m_data + i) T(val); m_used = n; return true; } bool resize(unsigned n) { return resize(n, T()); } T* begin() { return m_data; } T* end() { return m_data + m_used; } T* begin()const { return m_data; } T* end()const { return m_data + m_used; } unsigned capacity()const { return m_capacity; } void clear() { m_used = 0; } private: T* m_data; unsigned m_used {}; unsigned m_capacity; }; template class bernoulli_numbers_cache { public: bernoulli_numbers_cache() : m_overflow_limit((std::numeric_limits::max)()) , m_counter(0) , m_current_precision(boost::math::tools::digits()) {} using container_type = fixed_vector; bool tangent(std::size_t m) { static const std::size_t min_overflow_index = b2n_overflow_limit() - 1; if (!tn.resize(static_cast(m), T(0U))) { return false; } BOOST_MATH_INSTRUMENT_VARIABLE(min_overflow_index); std::size_t prev_size = m_intermediates.size(); m_intermediates.resize(m, T(0U)); if(prev_size == 0) { m_intermediates[1] = tangent_scale_factor() /*T(1U)*/; tn[0U] = T(0U); tn[1U] = tangent_scale_factor()/* T(1U)*/; BOOST_MATH_INSTRUMENT_VARIABLE(tn[0]); BOOST_MATH_INSTRUMENT_VARIABLE(tn[1]); } for(std::size_t i = std::max(2, prev_size); i < m; i++) { bool overflow_check = false; if(i >= min_overflow_index && (boost::math::tools::max_value() / (i-1) < m_intermediates[1]) ) { std::fill(tn.begin() + i, tn.end(), boost::math::tools::max_value()); break; } m_intermediates[1] = m_intermediates[1] * (i-1); for(std::size_t j = 2; j <= i; j++) { overflow_check = (i >= min_overflow_index) && ( (boost::math::tools::max_value() / (i - j) < m_intermediates[j]) || (boost::math::tools::max_value() / (i - j + 2) < m_intermediates[j-1]) || (boost::math::tools::max_value() - m_intermediates[j] * (i - j) < m_intermediates[j-1] * (i - j + 2)) || ((boost::math::isinf)(m_intermediates[j])) ); if(overflow_check) { std::fill(tn.begin() + i, tn.end(), boost::math::tools::max_value()); break; } m_intermediates[j] = m_intermediates[j] * (i - j) + m_intermediates[j-1] * (i - j + 2); } if(overflow_check) break; // already filled the tn... tn[static_cast(i)] = m_intermediates[i]; BOOST_MATH_INSTRUMENT_VARIABLE(i); BOOST_MATH_INSTRUMENT_VARIABLE(tn[static_cast(i)]); } return true; } bool tangent_numbers_series(const std::size_t m) { BOOST_MATH_STD_USING static const std::size_t min_overflow_index = b2n_overflow_limit() - 1; typename container_type::size_type old_size = bn.size(); if (!tangent(m)) return false; if (!bn.resize(static_cast(m))) return false; if(!old_size) { bn[0] = 1; old_size = 1; } T power_two(ldexp(T(1), static_cast(2 * old_size))); for(std::size_t i = old_size; i < m; i++) { T b(static_cast(i * 2)); // // Not only do we need to take care to avoid spurious over/under flow in // the calculation, but we also need to avoid overflow altogether in case // we're calculating with a type where "bad things" happen in that case: // b = b / (power_two * tangent_scale_factor()); b /= (power_two - 1); bool overflow_check = (i >= min_overflow_index) && (tools::max_value() / tn[static_cast(i)] < b); if(overflow_check) { m_overflow_limit = i; while(i < m) { b = std::numeric_limits::has_infinity ? std::numeric_limits::infinity() : tools::max_value(); bn[static_cast(i)] = ((i % 2U) ? b : T(-b)); ++i; } break; } else { b *= tn[static_cast(i)]; } power_two = ldexp(power_two, 2); const bool b_neg = i % 2 == 0; bn[static_cast(i)] = ((!b_neg) ? b : T(-b)); } return true; } template OutputIterator copy_bernoulli_numbers(OutputIterator out, std::size_t start, std::size_t n, const Policy& pol) { // // There are basically 3 thread safety options: // // 1) There are no threads (BOOST_MATH_HAS_THREADS is not defined). // 2) There are threads, but we do not have a true atomic integer type, // in this case we just use a mutex to guard against race conditions. // 3) There are threads, and we have an atomic integer: in this case we can // use the double-checked locking pattern to avoid thread synchronisation // when accessing values already in the cache. // // First off handle the common case for overflow and/or asymptotic expansion: // if(start + n > bn.capacity()) { if(start < bn.capacity()) { out = copy_bernoulli_numbers(out, start, bn.capacity() - start, pol); n -= bn.capacity() - start; start = static_cast(bn.capacity()); } if(start < b2n_overflow_limit() + 2u) { for(; n; ++start, --n) { *out = b2n_asymptotic(static_cast(start * 2U)); ++out; } } for(; n; ++start, --n) { *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(start), pol); ++out; } return out; } #if defined(BOOST_MATH_HAS_THREADS) && defined(BOOST_MATH_BERNOULLI_NOTHREADS) && !defined(BOOST_MATH_BERNOULLI_UNTHREADED) // Add a static_assert on instantiation if we have threads, but no C++11 threading support. static_assert(sizeof(T) == 1, "Unsupported configuration: your platform appears to have either no atomic integers, or no std::mutex. If you are happy with thread-unsafe code, then you may define BOOST_MATH_BERNOULLI_UNTHREADED to suppress this error."); #elif defined(BOOST_MATH_BERNOULLI_NOTHREADS) // // Single threaded code, very simple: // if(m_current_precision < boost::math::tools::digits()) { bn.clear(); tn.clear(); m_intermediates.clear(); m_current_precision = boost::math::tools::digits(); } if(start + n >= bn.size()) { std::size_t new_size = (std::min)((std::max)((std::max)(std::size_t(start + n), std::size_t(bn.size() + 20)), std::size_t(50)), std::size_t(bn.capacity())); if (!tangent_numbers_series(new_size)) { return std::fill_n(out, n, policies::raise_evaluation_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", "Unable to allocate Bernoulli numbers cache for %1% values", T(start + n), pol)); } } for(std::size_t i = (std::max)(std::size_t(max_bernoulli_b2n::value + 1), start); i < start + n; ++i) { *out = (i >= m_overflow_limit) ? policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol) : bn[i]; ++out; } #else // // Double-checked locking pattern, lets us access cached already cached values // without locking: // // Get the counter and see if we need to calculate more constants: // if((static_cast(m_counter.load(std::memory_order_consume)) < start + n) || (static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits())) { std::lock_guard l(m_mutex); if((static_cast(m_counter.load(std::memory_order_consume)) < start + n) || (static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits())) { if(static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits()) { bn.clear(); tn.clear(); m_intermediates.clear(); m_counter.store(0, std::memory_order_release); m_current_precision = boost::math::tools::digits(); } if(start + n >= bn.size()) { std::size_t new_size = (std::min)((std::max)((std::max)(std::size_t(start + n), std::size_t(bn.size() + 20)), std::size_t(50)), std::size_t(bn.capacity())); if (!tangent_numbers_series(new_size)) return std::fill_n(out, n, policies::raise_evaluation_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", "Unable to allocate Bernoulli numbers cache for %1% values", T(new_size), pol)); } m_counter.store(static_cast(bn.size()), std::memory_order_release); } } for(std::size_t i = (std::max)(static_cast(max_bernoulli_b2n::value + 1), start); i < start + n; ++i) { *out = (i >= m_overflow_limit) ? policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol) : bn[static_cast(i)]; ++out; } #endif // BOOST_MATH_HAS_THREADS return out; } template OutputIterator copy_tangent_numbers(OutputIterator out, std::size_t start, std::size_t n, const Policy& pol) { // // There are basically 3 thread safety options: // // 1) There are no threads (BOOST_MATH_HAS_THREADS is not defined). // 2) There are threads, but we do not have a true atomic integer type, // in this case we just use a mutex to guard against race conditions. // 3) There are threads, and we have an atomic integer: in this case we can // use the double-checked locking pattern to avoid thread synchronisation // when accessing values already in the cache. // // // First off handle the common case for overflow and/or asymptotic expansion: // if(start + n > bn.capacity()) { if(start < bn.capacity()) { out = copy_tangent_numbers(out, start, bn.capacity() - start, pol); n -= bn.capacity() - start; start = static_cast(bn.capacity()); } if(start < b2n_overflow_limit() + 2u) { for(; n; ++start, --n) { *out = t2n_asymptotic(static_cast(start)); ++out; } } for(; n; ++start, --n) { *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", 0, T(start), pol); ++out; } return out; } #if defined(BOOST_MATH_BERNOULLI_NOTHREADS) // // Single threaded code, very simple: // if(m_current_precision < boost::math::tools::digits()) { bn.clear(); tn.clear(); m_intermediates.clear(); m_current_precision = boost::math::tools::digits(); } if(start + n >= bn.size()) { std::size_t new_size = (std::min)((std::max)((std::max)(start + n, std::size_t(bn.size() + 20)), std::size_t(50)), std::size_t(bn.capacity())); if (!tangent_numbers_series(new_size)) return std::fill_n(out, n, policies::raise_evaluation_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", "Unable to allocate Bernoulli numbers cache for %1% values", T(start + n), pol)); } for(std::size_t i = start; i < start + n; ++i) { if(i >= m_overflow_limit) *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol); else { if(tools::max_value() * tangent_scale_factor() < tn[static_cast(i)]) *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol); else *out = tn[static_cast(i)] / tangent_scale_factor(); } ++out; } #elif defined(BOOST_MATH_NO_ATOMIC_INT) static_assert(sizeof(T) == 1, "Unsupported configuration: your platform appears to have no atomic integers. If you are happy with thread-unsafe code, then you may define BOOST_MATH_BERNOULLI_UNTHREADED to suppress this error."); #else // // Double-checked locking pattern, lets us access cached already cached values // without locking: // // Get the counter and see if we need to calculate more constants: // if((static_cast(m_counter.load(std::memory_order_consume)) < start + n) || (static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits())) { std::lock_guard l(m_mutex); if((static_cast(m_counter.load(std::memory_order_consume)) < start + n) || (static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits())) { if(static_cast(m_current_precision.load(std::memory_order_consume)) < boost::math::tools::digits()) { bn.clear(); tn.clear(); m_intermediates.clear(); m_counter.store(0, std::memory_order_release); m_current_precision = boost::math::tools::digits(); } if(start + n >= bn.size()) { std::size_t new_size = (std::min)((std::max)((std::max)(start + n, std::size_t(bn.size() + 20)), std::size_t(50)), std::size_t(bn.capacity())); if (!tangent_numbers_series(new_size)) return std::fill_n(out, n, policies::raise_evaluation_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", "Unable to allocate Bernoulli numbers cache for %1% values", T(start + n), pol)); } m_counter.store(static_cast(bn.size()), std::memory_order_release); } } for(std::size_t i = start; i < start + n; ++i) { if(i >= m_overflow_limit) *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol); else { if(tools::max_value() * tangent_scale_factor() < tn[static_cast(i)]) *out = policies::raise_overflow_error("boost::math::bernoulli_b2n<%1%>(std::size_t)", nullptr, T(i), pol); else *out = tn[static_cast(i)] / tangent_scale_factor(); } ++out; } #endif // BOOST_MATH_HAS_THREADS return out; } private: // // The caches for Bernoulli and tangent numbers, once allocated, // these must NEVER EVER reallocate as it breaks our thread // safety guarantees: // fixed_vector bn, tn; std::vector m_intermediates; // The value at which we know overflow has already occurred for the Bn: std::size_t m_overflow_limit; #if !defined(BOOST_MATH_BERNOULLI_NOTHREADS) std::mutex m_mutex; atomic_counter_type m_counter, m_current_precision; #else int m_counter; int m_current_precision; #endif // BOOST_MATH_HAS_THREADS }; template inline typename std::enable_if<(std::numeric_limits::digits == 0) || (std::numeric_limits::digits >= INT_MAX), bernoulli_numbers_cache&>::type get_bernoulli_numbers_cache() { // // When numeric_limits<>::digits is zero, the type has either not specialized numeric_limits at all // or it's precision can vary at runtime. So make the cache thread_local so that each thread can // have it's own precision if required: // static #ifndef BOOST_MATH_NO_THREAD_LOCAL_WITH_NON_TRIVIAL_TYPES BOOST_MATH_THREAD_LOCAL #endif bernoulli_numbers_cache data; return data; } template inline typename std::enable_if::digits && (std::numeric_limits::digits < INT_MAX), bernoulli_numbers_cache&>::type get_bernoulli_numbers_cache() { // // Note that we rely on C++11 thread-safe initialization here: // static bernoulli_numbers_cache data; return data; } }}} #endif // BOOST_MATH_BERNOULLI_DETAIL_HPP