Skip to main content

malachite_float/float/constants/
eulers_constant.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MPFR Library.
4//
5//      Copyright 2001-2025 Free Software Foundation, Inc.
6//
7//      Contributed by Fredrik Johansson.
8//
9// This file is part of Malachite.
10//
11// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
12// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
13// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
14
15use crate::Float;
16use core::cmp::Ordering;
17use malachite_base::num::arithmetic::traits::{
18    AddMul, CeilingLogBase2, DivRound, MulAddMul, Pow, Square,
19};
20use malachite_base::num::basic::integers::PrimitiveInt;
21use malachite_base::num::basic::traits::{One, Zero};
22use malachite_base::num::conversion::traits::ExactFrom;
23use malachite_base::rounding_modes::RoundingMode::{self, *};
24use malachite_nz::natural::Natural;
25use malachite_nz::natural::arithmetic::float::round::float_can_round;
26use malachite_nz::platform::Limb;
27use malachite_q::Rational;
28
29// The six binary-splitting components. This is mpfr_const_euler_bs_struct from const_euler.c, MPFR
30// 4.2.2.
31struct SplitState {
32    p: Natural,
33    q: Natural,
34    t: Natural,
35    c: Natural,
36    d: Natural,
37    v: Natural,
38}
39
40// Six-component binary splitting over [n1, n2) for the sums
41//
42// S = sum_{k=0}^{N-1} H_k n^(2k) / (k!)^2 and I = sum_{k=0}^{N-1} n^(2k) / (k!)^2,
43//
44// where H_k is the k-th harmonic number; after the top-level call, V/((T+Q)D) = S/I. Every leaf's P
45// is the same n^2, so the caller computes it once and passes it in. When `cont` is false (at the
46// top level), the P and C components are not needed and are returned as zero.
47//
48// This is mpfr_const_euler_bs_1 from const_euler.c, MPFR 4.2.2.
49fn s1(n1: u64, n2: u64, n_squared: &Natural, cont: bool) -> SplitState {
50    if n2 - n1 == 1 {
51        let d = Natural::from(n1 + 1);
52        let q = (&d).square();
53        SplitState {
54            p: n_squared.clone(),
55            q,
56            t: n_squared.clone(),
57            c: Natural::ONE,
58            d,
59            v: n_squared.clone(),
60        }
61    } else {
62        let m = (n1 + n2) >> 1;
63        let l = s1(n1, m, n_squared, true);
64        let r = s1(m, n2, n_squared, true);
65        // t = LP RT is shared between the T and V combinations
66        let t = &l.p * r.t;
67        SplitState {
68            // T = LP RT + RQ LT
69            t: (&t).add_mul(&r.q, &l.t),
70            // C = LC RD + RC LD
71            c: if cont {
72                (r.c * &l.d).add_mul(&l.c, &r.d)
73            } else {
74                Natural::ZERO
75            },
76            // V = RD (RQ LV + LC LP RT) + LD LP RV
77            v: (&r.q * l.v)
78                .add_mul(t, l.c)
79                .mul_add_mul(&r.d, &l.p * r.v, &l.d),
80            p: if cont { l.p * r.p } else { Natural::ZERO },
81            q: l.q * r.q,
82            d: l.d * r.d,
83        }
84    }
85}
86
87// Three-component binary splitting over [n1, n2) for the sum
88//
89// U = (1/(4n)) sum_{k=0}^{2n-1} [(2k)!]^3 / ((k!)^4 8^(2k) (2n)^(2k)),
90//
91// with T/Q = the sum after the top-level call. The leaves' N^2 factor is the same everywhere, so
92// the caller computes it once and passes it in. When `cont` is false (at the top level), the P
93// component is not needed and is returned as zero.
94//
95// This is mpfr_const_euler_bs_2 from const_euler.c, MPFR 4.2.2.
96fn s2(
97    n1: u64,
98    n2: u64,
99    big_n: u64,
100    n_squared: &Natural,
101    cont: bool,
102) -> (Natural, Natural, Natural) {
103    if n2 - n1 == 1 {
104        if n1 == 0 {
105            (Natural::ONE, Natural::from(big_n) << 2u32, Natural::ONE)
106        } else {
107            let p = Natural::from((n1 << 1) - 1).pow(3);
108            let q = (Natural::from(n1) * n_squared) << 5u32;
109            (p.clone(), q, p)
110        }
111    } else {
112        let m = (n1 + n2) >> 1;
113        let (p, q, t) = s2(n1, m, big_n, n_squared, true);
114        let (p2, q2, t2) = s2(m, n2, big_n, n_squared, true);
115        let big_t = (t * &q2).add_mul(t2, &p);
116        (if cont { p * p2 } else { Natural::ZERO }, q * q2, big_t)
117    }
118}
119
120impl Float {
121    /// Returns an approximation of Euler's constant (also known as the Euler–Mascheroni
122    /// constant), $\gamma=\lim_{n\to\infty}\left(\sum_{k=1}^n\frac{1}{k}-\log n\right)$, with the
123    /// given precision and rounded using the given [`RoundingMode`]. An [`Ordering`] is also
124    /// returned, indicating whether the rounded value is less than or greater than the exact value
125    /// of the constant. (The rounded value is never equal to the exact value of the constant.
126    /// Euler's constant has not been proven irrational, but its binary expansion is known to be
127    /// aperiodic far beyond any precision reachable by this function.)
128    ///
129    /// $$
130    /// x = \gamma+\varepsilon.
131    /// $$
132    /// - If $m$ is not `Nearest`, then $|\varepsilon| < 2^{-p+1}$.
133    /// - If $m$ is `Nearest`, then $|\varepsilon| < 2^{-p}$.
134    ///
135    /// The output has precision `prec`.
136    ///
137    /// # Worst-case complexity
138    /// $T(n) = O(n (\log n)^2 \log\log n)$
139    ///
140    /// $M(n) = O(n \log n)$
141    ///
142    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
143    ///
144    /// # Panics
145    /// Panics if `prec` is zero or if `rm` is `Exact`.
146    ///
147    /// # Examples
148    /// ```
149    /// use malachite_base::rounding_modes::RoundingMode::*;
150    /// use malachite_float::Float;
151    /// use std::cmp::Ordering::*;
152    ///
153    /// let (g, o) = Float::eulers_constant_prec_round(100, Floor);
154    /// assert_eq!(g.to_string(), "0.57721566490153286060651209008234");
155    /// assert_eq!(o, Less);
156    ///
157    /// let (g, o) = Float::eulers_constant_prec_round(100, Ceiling);
158    /// assert_eq!(g.to_string(), "0.57721566490153286060651209008313");
159    /// assert_eq!(o, Greater);
160    /// ```
161    ///
162    // Euler's constant is computed using the Brent-McMillan algorithm with binary splitting, as
163    // gamma = S/I - U/I^2 - log(n), with the approximation error bounded using Theorem 1 and Remark
164    // 2 of Fredrik Johansson's "Evaluation of the Bessel function sum ..." paper
165    // (https://arxiv.org/pdf/1312.0039v1.pdf).
166    //
167    // MPFR computes v - log(n) 2^wp over integers scaled by 2^wp, with the log and the subtraction
168    // rounded toward zero; here the scaling is folded away by subtracting the exact Rational v/2^wp
169    // from the log and negating, toward-zero rounding being symmetric under negation.
170    //
171    // This is mpfr_const_euler_internal from const_euler.c, MPFR 4.2.2.
172    pub fn eulers_constant_prec_round(prec: u64, rm: RoundingMode) -> (Self, Ordering) {
173        let mut wp = prec + prec.ceiling_log_base_2() + 5;
174        let mut increment = Limb::WIDTH;
175        loop {
176            // The approximation error is bounded by 24 exp(-8n) when n > 1, which is smaller than
177            // 2^-wp if n > (wp + log_2(24)) * (log(2)/8). Note log2(24) < 5 and log(2)/8 < 866434 /
178            // 10000000.
179            let n = u64::exact_from(
180                ((u128::from(wp) + 5) * 866434)
181                    .div_round(10000000, Ceiling)
182                    .0,
183            );
184            // It is sufficient to take N >= alpha*n + 1 where alpha = 3/LambertW(3/e) =
185            // 4.970625759544...
186            let big_n =
187                u64::exact_from((u128::from(n) * 4970626).div_round(1000000, Ceiling).0) + 1;
188            let n_squared = Natural::from(n).square();
189            // V / ((T + Q) * D) = S / I
190            let sum = s1(0, big_n, &n_squared, false);
191            let t = sum.t + &sum.q;
192            // s_over_i * 2^-wp = S/I with error < 1
193            let s_over_i = (sum.v << wp) / (&t * sum.d);
194            // T2/Q2 = 4n U after the top-level call, and u_over_i_squared * 2^-wp = U/I^2 with
195            // error < 1
196            let (_, q2, t2) = s2(0, n << 1, n, &n_squared, false);
197            let u_over_i_squared = ((sum.q.square() * t2) << wp) / (t.square() * q2);
198            // v * 2^-wp = gamma + log(n) with error at most 3*2^-wp
199            let v = s_over_i - u_over_i_squared;
200            // log(n) < 2^ceil(log2(n))
201            let magn = n.ceiling_log_base_2();
202            // y = gamma with error < 5*2^-wp
203            let y = -(Self::ln_unsigned_prec_round(n, wp + magn, Down)
204                .0
205                .sub_rational_prec_round(Rational::from(v) >> wp, wp + magn, Down)
206                .0);
207            if float_can_round(y.significand_ref().unwrap(), wp - 3, prec, rm) {
208                return Self::from_float_prec_round(y, prec, rm);
209            }
210            wp += increment;
211            increment = wp >> 1;
212        }
213    }
214
215    /// Returns an approximation of Euler's constant (also known as the Euler–Mascheroni
216    /// constant), $\gamma=\lim_{n\to\infty}\left(\sum_{k=1}^n\frac{1}{k}-\log n\right)$, with the
217    /// given precision and rounded to the nearest [`Float`] of that precision. An [`Ordering`] is
218    /// also returned, indicating whether the rounded value is less than or greater than the exact
219    /// value of the constant. (The rounded value is never equal to the exact value of the constant.
220    /// Euler's constant has not been proven irrational, but its binary expansion is known to be
221    /// aperiodic far beyond any precision reachable by this function.)
222    ///
223    /// $$
224    /// x = \gamma+\varepsilon.
225    /// $$
226    /// - $|\varepsilon| < 2^{-p}$.
227    ///
228    /// The output has precision `prec`.
229    ///
230    /// # Worst-case complexity
231    /// $T(n) = O(n (\log n)^2 \log\log n)$
232    ///
233    /// $M(n) = O(n \log n)$
234    ///
235    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
236    ///
237    /// # Panics
238    /// Panics if `prec` is zero.
239    ///
240    /// # Examples
241    /// ```
242    /// use malachite_float::Float;
243    /// use std::cmp::Ordering::*;
244    ///
245    /// let (g, o) = Float::eulers_constant_prec(1);
246    /// assert_eq!(g.to_string(), "0.50");
247    /// assert_eq!(o, Less);
248    ///
249    /// let (g, o) = Float::eulers_constant_prec(10);
250    /// assert_eq!(g.to_string(), "0.57715");
251    /// assert_eq!(o, Less);
252    ///
253    /// let (g, o) = Float::eulers_constant_prec(100);
254    /// assert_eq!(g.to_string(), "0.57721566490153286060651209008234");
255    /// assert_eq!(o, Less);
256    /// ```
257    #[inline]
258    pub fn eulers_constant_prec(prec: u64) -> (Self, Ordering) {
259        Self::eulers_constant_prec_round(prec, Nearest)
260    }
261}