piranha  0.10
binomial.hpp
1 /* Copyright 2009-2017 Francesco Biscani (bluescarni@gmail.com)
2 
3 This file is part of the Piranha library.
4 
5 The Piranha library is free software; you can redistribute it and/or modify
6 it under the terms of either:
7 
8  * the GNU Lesser General Public License as published by the Free
9  Software Foundation; either version 3 of the License, or (at your
10  option) any later version.
11 
12 or
13 
14  * the GNU General Public License as published by the Free Software
15  Foundation; either version 3 of the License, or (at your option) any
16  later version.
17 
18 or both in parallel, as here.
19 
20 The Piranha library is distributed in the hope that it will be useful, but
21 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23 for more details.
24 
25 You should have received copies of the GNU General Public License and the
26 GNU Lesser General Public License along with the Piranha library. If not,
27 see https://www.gnu.org/licenses/. */
28 
29 #ifndef PIRANHA_BINOMIAL_HPP
30 #define PIRANHA_BINOMIAL_HPP
31 
32 #include <boost/math/constants/constants.hpp>
33 #include <cmath>
34 #include <stdexcept>
35 #include <type_traits>
36 #include <utility>
37 
38 #include <piranha/config.hpp>
39 #include <piranha/exceptions.hpp>
40 #include <piranha/mp_integer.hpp>
41 #include <piranha/pow.hpp>
42 #include <piranha/type_traits.hpp>
43 
44 namespace piranha
45 {
46 
47 inline namespace impl
48 {
49 
50 // Compute gamma(a)/(gamma(b) * gamma(c)), assuming a, b and c are not negative ints.
51 // This is a helper function for the implementation of binomial() for fp types.
52 template <typename T>
53 inline T compute_3_gamma(const T &a, const T &b, const T &c)
54 {
55  // Here we should never enter with negative ints.
56  piranha_assert(a >= T(0) || std::trunc(a) != a);
57  piranha_assert(b >= T(0) || std::trunc(b) != b);
58  piranha_assert(c >= T(0) || std::trunc(c) != c);
59  const T pi = boost::math::constants::pi<T>();
60  T tmp0(0), tmp1(1);
61  if (a < T(0)) {
62  tmp0 -= std::lgamma(T(1) - a);
63  tmp1 *= pi / std::sin(a * pi);
64  } else {
65  tmp0 += std::lgamma(a);
66  }
67  if (b < T(0)) {
68  tmp0 += std::lgamma(T(1) - b);
69  tmp1 *= std::sin(b * pi) / pi;
70  } else {
71  tmp0 -= std::lgamma(b);
72  }
73  if (c < T(0)) {
74  tmp0 += std::lgamma(T(1) - c);
75  tmp1 *= std::sin(c * pi) / pi;
76  } else {
77  tmp0 -= std::lgamma(c);
78  }
79  return std::exp(tmp0) * tmp1;
80 }
81 
82 // Implementation of the generalised binomial coefficient for floating-point types.
83 template <typename T>
84 inline T math_fp_binomial(const T &x, const T &y)
85 {
86  static_assert(std::is_floating_point<T>::value, "Invalid type for fp_binomial.");
87  if (unlikely(!std::isfinite(x) || !std::isfinite(y))) {
88  piranha_throw(std::invalid_argument,
89  "cannot compute binomial coefficient with non-finite floating-point argument(s)");
90  }
91  const bool neg_int_x = std::trunc(x + T(1)) == (x + T(1)) && (x + T(1)) <= T(0),
92  neg_int_y = std::trunc(y + T(1)) == (y + T(1)) && (y + T(1)) <= T(0),
93  neg_int_x_y = std::trunc(x - y + T(1)) == (x - y + T(1)) && (x - y + T(1)) <= T(0);
94  const unsigned mask = unsigned(neg_int_x) + (unsigned(neg_int_y) << 1u) + (unsigned(neg_int_x_y) << 2u);
95  switch (mask) {
96  case 0u:
97  // Case 0 is the non-special one, use the default implementation.
98  return compute_3_gamma(x + T(1), y + T(1), x - y + T(1));
99  // NOTE: case 1 is not possible: x < 0, y > 0 implies x - y < 0 always.
100  case 2u:
101  case 4u:
102  // These are finite numerators with infinite denominators.
103  return T(0.);
104  // NOTE: case 6 is not possible: x > 0, y < 0 implies x - y > 0 always.
105  case 3u: {
106  // 3 and 5 are the cases with 1 inf in num and 1 inf in den. Use the transformation
107  // formula to make them finite.
108  // NOTE: the phase here is really just a sign, but it seems tricky to compute this exactly
109  // due to potential rounding errors. We are attempting to err on the safe side by using pow()
110  // here.
111  const auto phase = std::pow(T(-1), x + T(1)) / std::pow(T(-1), y + T(1));
112  return compute_3_gamma(-y, -x, x - y + T(1)) * phase;
113  }
114  case 5u: {
115  const auto phase = std::pow(T(-1), x - y + T(1)) / std::pow(T(-1), x + T(1));
116  return compute_3_gamma(-(x - y), y + T(1), -x) * phase;
117  }
118  }
119  // Case 7 returns zero -> from inf / (inf * inf) it becomes a / (b * inf) after the transform.
120  // NOTE: put it here so the compiler does not complain about missing return statement in the switch block.
121  return T(0);
122 }
123 
124 // Enabler for the specialisation of binomial for floating-point and arithmetic arguments.
125 template <typename T, typename U>
126 using binomial_fp_arith_enabler
127  = enable_if_t<conjunction<std::is_arithmetic<T>, std::is_arithmetic<U>,
128  disjunction<std::is_floating_point<T>, std::is_floating_point<U>>>::value>;
129 }
130 
131 namespace math
132 {
133 
135 
139 template <typename T, typename U, typename = void>
141 };
142 
144 
148 template <typename T, typename U>
149 struct binomial_impl<T, U, binomial_fp_arith_enabler<T, U>> {
151 
154  using result_type = typename std::common_type<T, U>::type;
156 
169  result_type operator()(const T &x, const U &y) const
170  {
171  return math_fp_binomial(static_cast<result_type>(x), static_cast<result_type>(y));
172  }
173 };
174 }
175 
176 inline namespace impl
177 {
178 
179 // Determination and enabling of the return type for math::binomial().
180 template <typename T, typename U>
181 using math_binomial_type_ = decltype(math::binomial_impl<T, U>{}(std::declval<const T &>(), std::declval<const U &>()));
182 
183 template <typename T, typename U>
184 using math_binomial_type = enable_if_t<is_returnable<math_binomial_type_<T, U>>::value, math_binomial_type_<T, U>>;
185 }
186 
187 namespace math
188 {
189 
191 
214 template <typename T, typename U>
215 inline math_binomial_type<T, U> binomial(const T &x, const U &y)
216 {
217  return binomial_impl<T, U>{}(x, y);
218 }
219 }
220 
221 inline namespace impl
222 {
223 
224 // Enabler for the binomial overload involving integral types, the same as pow.
225 template <typename T, typename U>
226 using integer_binomial_enabler = integer_pow_enabler<T, U>;
227 
228 // Wrapper for ADL.
229 template <typename T, typename U>
230 auto mp_integer_binomial_wrapper(const T &base, const U &exp) -> decltype(binomial(base, exp))
231 {
232  return binomial(base, exp);
233 }
234 }
235 
236 namespace math
237 {
238 
240 
254 template <typename T, typename U>
255 struct binomial_impl<T, U, integer_binomial_enabler<T, U>> {
256 private:
257  // integral--integral overload.
258  template <typename T2, typename U2,
259  enable_if_t<conjunction<std::is_integral<T2>, std::is_integral<U2>>::value, int> = 0>
260  static integer impl(const T2 &x, const U2 &y)
261  {
262  return mp_integer_binomial_wrapper(integer{x}, y);
263  }
264  // mp_integer--integral, integral--mp_integer, mp_integer--mp_integer overload.
265  template <typename T2, typename U2, enable_if_t<disjunction<conjunction<is_mp_integer<T2>, std::is_integral<U2>>,
266  conjunction<is_mp_integer<U2>, std::is_integral<T2>>,
267  is_same_mp_integer<T2, U2>>::value,
268  int> = 0>
269  static auto impl(const T2 &x, const U2 &y) -> decltype(mp_integer_binomial_wrapper(x, y))
270  {
271  return mp_integer_binomial_wrapper(x, y);
272  }
273  // fp--mp_integer overload.
274  template <typename T2, typename U2,
275  enable_if_t<conjunction<std::is_floating_point<T2>, is_mp_integer<U2>>::value, int> = 0>
276  static T2 impl(const T2 &x, const U2 &y)
277  {
278  return math::binomial(x, static_cast<T2>(y));
279  }
280  // mp_integer--fp overload.
281  template <typename T2, typename U2,
282  enable_if_t<conjunction<std::is_floating_point<U2>, is_mp_integer<T2>>::value, int> = 0>
283  static U2 impl(const T2 &x, const U2 &y)
284  {
285  return math::binomial(static_cast<U2>(x), y);
286  }
287  // Return type.
288  using ret_type = decltype(impl(std::declval<const T &>(), std::declval<const U &>()));
289 
290 public:
292 
300  ret_type operator()(const T &x, const U &y) const
301  {
302  return impl(x, y);
303  }
304 };
305 }
306 
308 
312 template <typename T, typename U>
314 {
315  template <typename T2, typename U2>
316  using binomial_t = decltype(math::binomial(std::declval<const T2 &>(), std::declval<const U2 &>()));
317  static const bool implementation_defined = is_detected<binomial_t, T, U>::value;
318 
319 public:
321  static const bool value = implementation_defined;
322 };
323 
324 // Static init.
325 template <typename T, typename U>
326 const bool has_binomial<T, U>::value;
327 }
328 
329 #endif
Multiprecision integer class.
Definition: mp++.hpp:869
result_type operator()(const T &x, const U &y) const
Call operator.
Definition: binomial.hpp:169
Default functor for the implementation of piranha::math::binomial().
Definition: binomial.hpp:140
static const bool value
Value of the type trait.
Definition: binomial.hpp:321
Exceptions.
Type trait to detect the presence of the piranha::math::binomial() function.
Definition: binomial.hpp:313
#define piranha_throw(exception_type,...)
Exception-throwing macro.
Definition: exceptions.hpp:118
math_binomial_type< T, U > binomial(const T &x, const U &y)
Generalised binomial coefficient.
Definition: binomial.hpp:215
Root piranha namespace.
Definition: array_key.hpp:52
Type traits.
typename std::common_type< T, U >::type result_type
Result type for the call operator.
Definition: binomial.hpp:154
ret_type operator()(const T &x, const U &y) const
Call operator.
Definition: binomial.hpp:300