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}