malachite_base/num/arithmetic/
binomial_coefficient.rs

1// Copyright © 2025 Mikhail Hogrefe
2//
3// This file is part of Malachite.
4//
5// Malachite is free software: you can redistribute it and/or modify it under the terms of the GNU
6// Lesser General Public License (LGPL) as published by the Free Software Foundation; either version
7// 3 of the License, or (at your option) any later version. See <https://www.gnu.org/licenses/>.
8
9use crate::num::arithmetic::traits::{
10    BinomialCoefficient, CheckedBinomialCoefficient, UnsignedAbs,
11};
12use crate::num::basic::signeds::PrimitiveSigned;
13use crate::num::basic::unsigneds::PrimitiveUnsigned;
14use crate::num::conversion::traits::OverflowingFrom;
15use crate::num::exhaustive::primitive_int_increasing_inclusive_range;
16use core::cmp::min;
17
18fn checked_binomial_coefficient_unsigned<T: PrimitiveUnsigned>(n: T, mut k: T) -> Option<T> {
19    if k > n {
20        return Some(T::ZERO);
21    }
22    k = min(k, n - k);
23    if k == T::ZERO {
24        Some(T::ONE)
25    } else if k == T::ONE {
26        Some(n)
27    } else if k == T::TWO {
28        (if n.even() { n - T::ONE } else { n }).checked_mul(n >> 1)
29    } else {
30        // Some binomial coefficient algorithms have intermediate results greater than the final
31        // result, risking overflow. This one does not.
32        let mut product = n - k + T::ONE;
33        let mut numerator = product;
34        for i in primitive_int_increasing_inclusive_range(T::TWO, k) {
35            numerator += T::ONE;
36            let gcd = numerator.gcd(i);
37            product /= i / gcd;
38            product = product.checked_mul(numerator / gcd)?;
39        }
40        Some(product)
41    }
42}
43
44fn checked_binomial_coefficient_signed<
45    U: PrimitiveUnsigned,
46    S: OverflowingFrom<U> + PrimitiveSigned + TryFrom<U> + UnsignedAbs<Output = U>,
47>(
48    n: S,
49    k: S,
50) -> Option<S> {
51    if k < S::ZERO {
52        return None;
53    }
54    if n >= S::ZERO {
55        S::try_from(U::checked_binomial_coefficient(
56            n.unsigned_abs(),
57            k.unsigned_abs(),
58        )?)
59        .ok()
60    } else {
61        let k = k.unsigned_abs();
62        let b = U::checked_binomial_coefficient(n.unsigned_abs() + k - U::ONE, k)?;
63        if k.even() {
64            S::try_from(b).ok()
65        } else {
66            let (b, overflow) = S::overflowing_from(b);
67            if overflow {
68                if b == S::MIN { Some(S::MIN) } else { None }
69            } else {
70                Some(-b)
71            }
72        }
73    }
74}
75
76macro_rules! impl_binomial_coefficient_unsigned {
77    ($t:ident) => {
78        impl CheckedBinomialCoefficient for $t {
79            /// Computes the binomial coefficient of two numbers. If the inputs are too large, the
80            /// function returns `None`.
81            ///
82            /// $$
83            /// f(n, k) = \\begin{cases}
84            ///     \operatorname{Some}(\binom{n}{k}) & \text{if} \\quad \binom{n}{k} < 2^W, \\\\
85            ///     \operatorname{None} & \text{if} \\quad \binom{n}{k} \geq 2^W,
86            /// \\end{cases}
87            /// $$
88            /// where $W$ is `Self::WIDTH`.
89            ///
90            /// # Worst-case complexity
91            /// $T(k) = O(k)$
92            ///
93            /// $M(k) = O(1)$
94            ///
95            /// where $T$ is time, $M$ is additional memory, and $k$ is `k`.
96            ///
97            /// # Examples
98            /// See [here](super::binomial_coefficient#checked_binomial_coefficient).
99            #[inline]
100            fn checked_binomial_coefficient(n: $t, k: $t) -> Option<$t> {
101                checked_binomial_coefficient_unsigned(n, k)
102            }
103        }
104    };
105}
106apply_to_unsigneds!(impl_binomial_coefficient_unsigned);
107
108macro_rules! impl_binomial_coefficient_signed {
109    ($t:ident) => {
110        impl CheckedBinomialCoefficient for $t {
111            /// Computes the binomial coefficient of two numbers. If the inputs are too large, the
112            /// function returns `None`.
113            ///
114            /// The second argument must be non-negative, but the first may be negative. If it is,
115            /// the identity $\binom{-n}{k} = (-1)^k \binom{n+k-1}{k}$ is used.
116            ///
117            /// $$
118            /// f(n, k) = \\begin{cases}
119            ///     \operatorname{Some}(\binom{n}{k}) & \text{if} \\quad n \geq 0 \\ \text{and}
120            ///         \\ -2^{W-1} \leq \binom{n}{k} < 2^{W-1}, \\\\
121            ///     \operatorname{Some}((-1)^k \binom{-n+k-1}{k}) & \text{if} \\quad n < 0
122            ///         \\ \text{and} \\ -2^{W-1} \leq \binom{n}{k} < 2^{W-1}, \\\\
123            ///     \operatorname{None} & \\quad \\text{otherwise},
124            /// \\end{cases}
125            /// $$
126            /// where $W$ is `Self::WIDTH`.
127            ///
128            /// # Worst-case complexity
129            /// $T(k) = O(k)$
130            ///
131            /// $M(k) = O(1)$
132            ///
133            /// where $T$ is time, $M$ is additional memory, and $k$ is `k.abs()`.
134            ///
135            /// # Examples
136            /// See [here](super::binomial_coefficient#checked_binomial_coefficient).
137            #[inline]
138            fn checked_binomial_coefficient(n: $t, k: $t) -> Option<$t> {
139                checked_binomial_coefficient_signed(n, k)
140            }
141        }
142    };
143}
144apply_to_signeds!(impl_binomial_coefficient_signed);
145
146macro_rules! impl_binomial_coefficient_primitive_int {
147    ($t:ident) => {
148        impl BinomialCoefficient for $t {
149            /// Computes the binomial coefficient of two numbers. If the inputs are too large, the
150            /// function panics.
151            ///
152            /// The second argument must be non-negative, but the first may be negative. If it is,
153            /// the identity $\binom{-n}{k} = (-1)^k \binom{n+k-1}{k}$ is used.
154            ///
155            /// $$
156            /// f(n, k) = \\begin{cases}
157            ///     \binom{n}{k} & \text{if} \\quad n \geq 0, \\\\
158            ///     (-1)^k \binom{-n+k-1}{k} & \text{if} \\quad n < 0.
159            /// \\end{cases}
160            /// $$
161            ///
162            /// # Worst-case complexity
163            /// $T(k) = O(k)$
164            ///
165            /// $M(k) = O(1)$
166            ///
167            /// where $T$ is time, $M$ is additional memory, and $k$ is `k.abs()`.
168            ///
169            /// # Panics
170            /// Panics if the result is not representable by this type, or if $k$ is negative.
171            ///
172            /// # Examples
173            /// See [here](super::binomial_coefficient#binomial_coefficient).
174            #[inline]
175            fn binomial_coefficient(n: $t, k: $t) -> $t {
176                $t::checked_binomial_coefficient(n, k).unwrap()
177            }
178        }
179    };
180}
181apply_to_primitive_ints!(impl_binomial_coefficient_primitive_int);