Skip to main content

malachite_float/float/constants/
catalans_constant.rs

1// Copyright © 2026 Mikhail Hogrefe
2//
3// Uses code adopted from the GNU MPFR Library.
4//
5//      Copyright 2005-2025 Free Software Foundation, Inc.
6//
7//      Contributed by the Pascaline and Caramba projects, INRIA.
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::{AddMul, CeilingLogBase2, Square};
18use malachite_base::num::basic::integers::PrimitiveInt;
19use malachite_base::num::basic::traits::{One, Two};
20use malachite_base::rounding_modes::RoundingMode::{self, *};
21use malachite_nz::natural::Natural;
22use malachite_nz::natural::arithmetic::float::round::float_can_round;
23use malachite_nz::platform::Limb;
24
25// Returns (T, P, Q) such that T/Q = sum(k!^2/(2k)!/(2k+1)^2, k=n1..n2-1).
26//
27// This is S from const_catalan.c, MPFR 4.2.2.
28fn s(n1: u64, n2: u64) -> (Natural, Natural, Natural) {
29    if n2 == n1 + 1 {
30        if n1 == 0 {
31            (Natural::ONE, Natural::ONE, Natural::ONE)
32        } else {
33            let p = Natural::from((n1 << 1) - 1) * Natural::from(n1);
34            let q = Natural::from((n1 << 1) + 1).square() << 1u32;
35            (p.clone(), p, q)
36        }
37    } else {
38        let m = (n1 + n2) >> 1;
39        let (t, p, q) = s(n1, m);
40        let (t2, p2, q2) = s(m, n2);
41        ((t * &q2).add_mul(t2, &p), p * p2, q * q2)
42    }
43}
44
45impl Float {
46    /// Returns an approximation of Catalan's constant, $G=\sum_{k=0}^\infty
47    /// \frac{(-1)^k}{(2k+1)^2}$, with the given precision and rounded using the given
48    /// [`RoundingMode`]. An [`Ordering`] is also returned, indicating whether the rounded value is
49    /// less than or greater than the exact value of the constant. (The rounded value is never equal
50    /// to the exact value of the constant. Catalan's constant has not been proven irrational, but
51    /// its binary expansion is known to be aperiodic far beyond any precision reachable by this
52    /// function.)
53    ///
54    /// $$
55    /// x = G+\varepsilon.
56    /// $$
57    /// - If $m$ is not `Nearest`, then $|\varepsilon| < 2^{-p+1}$.
58    /// - If $m$ is `Nearest`, then $|\varepsilon| < 2^{-p}$.
59    ///
60    /// The output has precision `prec`.
61    ///
62    /// # Worst-case complexity
63    /// $T(n) = O(n (\log n)^2 \log\log n)$
64    ///
65    /// $M(n) = O(n \log n)$
66    ///
67    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
68    ///
69    /// # Panics
70    /// Panics if `prec` is zero or if `rm` is `Exact`.
71    ///
72    /// # Examples
73    /// ```
74    /// use malachite_base::rounding_modes::RoundingMode::*;
75    /// use malachite_float::Float;
76    /// use std::cmp::Ordering::*;
77    ///
78    /// let (g, o) = Float::catalans_constant_prec_round(100, Floor);
79    /// assert_eq!(g.to_string(), "0.91596559417721901505460351493173");
80    /// assert_eq!(o, Less);
81    ///
82    /// let (g, o) = Float::catalans_constant_prec_round(100, Ceiling);
83    /// assert_eq!(g.to_string(), "0.91596559417721901505460351493252");
84    /// assert_eq!(o, Greater);
85    /// ```
86    ///
87    // Catalan's constant is computed using formula (31) of Victor Adamchik's page "33
88    // representations for Catalan's constant":
89    //
90    // G = Pi/8*log(2+sqrt(3)) + 3/8*sum(k!^2/(2k)!/(2k+1)^2, k=0..infinity)
91    //
92    // This is mpfr_const_catalan_internal from const_catalan.c, MPFR 4.2.2.
93    pub fn catalans_constant_prec_round(prec: u64, rm: RoundingMode) -> (Self, Ordering) {
94        const THREE: Natural = Natural::const_from(3);
95        let mut working_prec = prec + prec.ceiling_log_base_2() + 7;
96        let mut increment = Limb::WIDTH;
97        loop {
98            // x = (pi * log(2 + sqrt(3)) + 3T/Q) / 8, where T/Q is the series computed by binary
99            // splitting. The log's argument and the series numerator are rounded up and the series
100            // denominator is rounded down; every operand has the working precision, so the
101            // arithmetic operators round to nearest at that precision.
102            let log_arg = Self::sqrt_unsigned_prec_round(3, working_prec, Up)
103                .0
104                .add_round(Self::TWO, Up)
105                .0;
106            let (t, _, q) = s(0, (working_prec - 1) >> 1);
107            let x = (Self::pi_prec_round(working_prec, Up).0 * log_arg.ln_round(Up).0
108                + Self::from_natural_prec_round(t * THREE, working_prec, Up).0
109                    / Self::from_natural_prec_round(q, working_prec, Down).0)
110                >> 3u32;
111            if float_can_round(x.significand_ref().unwrap(), working_prec - 5, prec, rm) {
112                return Self::from_float_prec_round(x, prec, rm);
113            }
114            working_prec += increment;
115            increment = working_prec >> 1;
116        }
117    }
118
119    /// Returns an approximation of Catalan's constant, $G=\sum_{k=0}^\infty
120    /// \frac{(-1)^k}{(2k+1)^2}$, with the given precision and rounded to the nearest [`Float`] of
121    /// that precision. An [`Ordering`] is also returned, indicating whether the rounded value is
122    /// less than or greater than the exact value of the constant. (The rounded value is never equal
123    /// to the exact value of the constant. Catalan's constant has not been proven irrational, but
124    /// its binary expansion is known to be aperiodic far beyond any precision reachable by this
125    /// function.)
126    ///
127    /// $$
128    /// x = G+\varepsilon.
129    /// $$
130    /// - $|\varepsilon| < 2^{-p}$.
131    ///
132    /// The output has precision `prec`.
133    ///
134    /// # Worst-case complexity
135    /// $T(n) = O(n (\log n)^2 \log\log n)$
136    ///
137    /// $M(n) = O(n \log n)$
138    ///
139    /// where $T$ is time, $M$ is additional memory, and $n$ is `prec`.
140    ///
141    /// # Panics
142    /// Panics if `prec` is zero.
143    ///
144    /// # Examples
145    /// ```
146    /// use malachite_float::Float;
147    /// use std::cmp::Ordering::*;
148    ///
149    /// let (g, o) = Float::catalans_constant_prec(1);
150    /// assert_eq!(g.to_string(), "1.0");
151    /// assert_eq!(o, Greater);
152    ///
153    /// let (g, o) = Float::catalans_constant_prec(10);
154    /// assert_eq!(g.to_string(), "0.91602");
155    /// assert_eq!(o, Greater);
156    ///
157    /// let (g, o) = Float::catalans_constant_prec(100);
158    /// assert_eq!(g.to_string(), "0.91596559417721901505460351493252");
159    /// assert_eq!(o, Greater);
160    /// ```
161    #[inline]
162    pub fn catalans_constant_prec(prec: u64) -> (Self, Ordering) {
163        Self::catalans_constant_prec_round(prec, Nearest)
164    }
165}