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);