// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // random_sampling.hpp // Standard floating-point (STL std::xxx_distribution) and multiprecision floating-point (sangi::Float): // a random-number sampling foundation that works with both. // // STL's std::uniform_real_distribution etc. require T to be a built-in floating-point type // (no Float support), so for multiprecision Float we build uniform randoms from 32-bit words // and implement each distribution via inverse-CDF / Box-Muller / Marsaglia-Tsang, etc. // // Each sampleXxxStd branches with if constexpr: standard floating-point uses STL as-is // (= keeps existing behavior and the random sequence unchanged); otherwise (Float) uses a custom implementation. #ifndef SANGI_RANDOM_SAMPLING_HPP #define SANGI_RANDOM_SAMPLING_HPP #include #include #include #include namespace sangi { namespace detail { // Uniform distribution [0, 1). For multiprecision Float, lay out mt19937 32-bit words up to the working precision // to build the mantissa (entropy >= working precision). template [[nodiscard]] inline T uniform01(std::mt19937& gen) { if constexpr (std::is_floating_point_v) { return std::uniform_real_distribution(T(0), T(1))(gen); } else { const int p = T::defaultPrecision(); // working precision (bits) const int words = p / 32 + 2; // number of 32-bit words (with margin) const T inv = T(1) / T(static_cast(1) << 32); // 2^-32 (exact) T result(0); T scale(1); for (int i = 0; i < words; ++i) { scale = scale * inv; result = result + T(static_cast(gen())) * scale; } return result; } } // Uniform distribution (0, 1] (excludes 0 so it can be used as a log argument). template [[nodiscard]] inline T uniform01_pos(std::mt19937& gen) { return T(1) - uniform01(gen); } // Standard normal distribution N(0,1). Box-Muller method (Float). template [[nodiscard]] inline T sampleNormalStd(std::mt19937& gen) { if constexpr (std::is_floating_point_v) { return std::normal_distribution(T(0), T(1))(gen); } else { T u1 = uniform01_pos(gen); // (0,1] T u2 = uniform01(gen); T r = generic_sqrt(T(-2) * generic_log(u1)); T theta = T(2) * generic_pi() * u2; return r * generic_cos(theta); } } // Standard exponential distribution (rate 1). Inverse CDF: -ln(1-u). template [[nodiscard]] inline T sampleExponentialStd(std::mt19937& gen) { if constexpr (std::is_floating_point_v) { return std::exponential_distribution(T(1))(gen); } else { return -generic_log(uniform01_pos(gen)); } } // Standard gamma distribution (shape a, scale 1). Marsaglia-Tsang method (Float). template [[nodiscard]] inline T sampleGammaStd(std::mt19937& gen, T a) { if constexpr (std::is_floating_point_v) { return std::gamma_distribution(a, T(1))(gen); } else { if (a < T(1)) { // a<1 is corrected by multiplying the Gamma(a+1) sample by U^{1/a} (Marsaglia-Tsang boost) T u = uniform01_pos(gen); return sampleGammaStd(gen, a + T(1)) * generic_pow(u, T(1) / a); } const T d = a - T(1) / T(3); const T c = T(1) / generic_sqrt(T(9) * d); for (;;) { T x = sampleNormalStd(gen); T v = T(1) + c * x; if (v <= T(0)) continue; v = v * v * v; T u = uniform01(gen); T x2 = x * x; if (u < T(1) - T(0.0331) * x2 * x2) return d * v; if (generic_log(u) < T(1) / T(2) * x2 + d * (T(1) - v + generic_log(v))) return d * v; } } } // Sample the Poisson distribution (mean lambda) by Knuth's method (for Float; returns a count as int). // For large lambda this takes O(lambda) iterations but is correct. template [[nodiscard]] inline int samplePoissonKnuth(std::mt19937& gen, T lambda) { const T L = generic_exp(-lambda); int k = 0; T p(1); do { ++k; p = p * uniform01(gen); } while (p > L); return k - 1; } } // namespace detail } // namespace sangi #endif // SANGI_RANDOM_SAMPLING_HPP