LogBinomialCoefficient.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.numbers.combinatorics;
import org.apache.commons.numbers.gamma.LogBeta;
/**
* Natural logarithm of the <a href="http://mathworld.wolfram.com/BinomialCoefficient.html">
* binomial coefficient</a>.
* It is "{@code n choose k}", the number of {@code k}-element subsets that
* can be selected from an {@code n}-element set.
*/
public final class LogBinomialCoefficient {
/** The maximum n that can be computed without overflow of a long for any m.
* {@code C(66, 33) < 2^63}. */
private static final int LIMIT_N_LONG = 66;
/** The maximum n that can be computed without overflow of a double for an m.
* C(1029, 514) ~ 1.43e308. */
private static final int LIMIT_N_DOUBLE = 1029;
/** The maximum m that can be computed without overflow of a double for any n.
* C(2147483647, 37) ~ 1.39e302. */
private static final int LIMIT_M_DOUBLE = 37;
/** Private constructor. */
private LogBinomialCoefficient() {
// intentionally empty.
}
/**
* Computes the logarithm of the binomial coefficient.
*
* <p>This returns a finite result for any valid {@code n choose k}.
*
* @param n Size of the set.
* @param k Size of the subsets to be counted.
* @return {@code log(n choose k)}.
* @throws IllegalArgumentException if {@code n < 0}, {@code k < 0} or {@code k > n}.
*/
public static double value(int n, int k) {
final int m = BinomialCoefficient.checkBinomial(n, k);
if (m == 0) {
return 0;
}
if (m == 1) {
return Math.log(n);
}
if (n <= LIMIT_N_LONG) {
// Delegate to the exact long result
return Math.log(BinomialCoefficient.value(n, k));
}
if (n <= LIMIT_N_DOUBLE || m <= LIMIT_M_DOUBLE) {
// Delegate to the double result
return Math.log(BinomialCoefficientDouble.value(n, k));
}
// n! gamma(n+1) gamma(k+1) * gamma(n-k+1)
// --------- = ------------------------- = 1 / -------------------------
// k! (n-k)! gamma(k+1) * gamma(n-k+1) gamma(n+1)
//
//
// = 1 / (k * beta(k, n-k+1))
//
// where: beta(a, b) = gamma(a) * gamma(b) / gamma(a+b)
// Delegate to LogBeta
return -Math.log(m) - LogBeta.value(m, n - m + 1);
}
}