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}