/*******************************************************************************
* Copyright 2014 Felipe Takiyama
*
* Licensed 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 br.usp.poli.takiyama.utils;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Math utilities not found in java.lang.Math.
* <br>
* I am not worried with efficiency, for now.
*
* @author ftakiyama
*
*/
public final class MathUtils {
public static final MathContext CONTEXT = MathContext.DECIMAL64;
public static int calls = 0;
private MathUtils() {
// avoids instantiation
}
/**
* @deprecated Because it cannot calculate factorial of numbers greater
* than 20
*
* @param n
* @return
* @throws IllegalArgumentException
*/
public static long factorial(int n) throws IllegalArgumentException {
long n_factorial = 1;
if (n == 1) return n_factorial;
if (n < 0) throw new IllegalArgumentException("Cannot calculate factorial of negative numbers!");
if (n > 20) throw new IllegalArgumentException("I can calculate up to 20!.");
for (int i = 2; i <= n; i++) {
n_factorial *= i;
}
return n_factorial;
}
/**
* Calculates the binomial coefficient C(n,k).
* <br>
* This method returns 0 if n = 0 and k > 0. It throws an
* IllegalArgumentException if any specified argument is negative.
*
* @param n A nonnegative integer
* @param k A nonnegative integer
* @return The binomial coefficient C(n,k).
* @throws IllegalArgumentException If the specified arguments are
* negative (at least one of them)
*/
public static BigInteger combination(int n, int k)
throws IllegalArgumentException {
if (n < 0 || k < 0) {
throw new IllegalArgumentException("Cannot calculate combination"
+ " for negative numbers.");
}
if (n == 0) {
return BigInteger.ZERO;
}
BigInteger r = BigInteger.ONE;
int nMinusK = n - k;
for (int i = 1; i <= k; i++) {
r = r.multiply(BigInteger.valueOf(nMinusK + i))
.divide(BigInteger.valueOf(i));
}
return r;
}
public static class Multinomial {
private final List<Integer> multi;
private Multinomial(List<? extends Integer> m) {
multi = new ArrayList<Integer>(m);
}
public static Multinomial getInstance(List<? extends Integer> m) {
return new Multinomial(m);
}
public int size() {
return multi.size();
}
int get(int index) {
return multi.get(index);
}
/**
* Subtracts 1 from the number at the specified position.
* @param index The position to decrement
* @return This tuple with the specified position decremented
*/
Multinomial decrement(int index) {
List<Integer> decremented = new ArrayList<Integer>(multi);
decremented.set(index, decremented.get(index) - 1);
return Multinomial.getInstance(decremented);
}
/**
* Returns <code>true</code> if this Multinomial only has positive
* values, <code>false</code> otherwise.
* @return <code>true</code> if this Multinomial only has positive
* values, <code>false</code> otherwise.
*/
boolean isValid() {
for (Integer i : multi) {
if (i.compareTo(Integer.valueOf(0)) < 0) {
return false;
}
}
return true;
}
/**
* Returns <code>true</code> if this Multinomial only has zero
* values, <code>false</code> otherwise.
* @return <code>true</code> if this Multinomial only has zero
* values, <code>false</code> otherwise.
*/
boolean isZeroed() {
for (Integer i : multi) {
if (i.compareTo(Integer.valueOf(0)) != 0) {
return false;
}
}
return true;
}
/**
* Sums all terms in the specified interval. The interval is closed,
* that is, the sum includes the indexes specified for the interval.
* @param fromIndex First index to sum
* @param toIndex Last index to sum
* @return The sum of all terms in the specified interval
*/
int sumTerms(int fromIndex, int toIndex) {
int result = 0;
for (int i = fromIndex; i <= toIndex; i++) {
result = result + multi.get(i);
}
return result;
}
@Override
public String toString() {
return multi.toString();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((multi == null) ? 0 : multi.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof Multinomial)) {
return false;
}
Multinomial other = (Multinomial) obj;
if (multi == null) {
if (other.multi != null) {
return false;
}
} else if (!multi.equals(other.multi)) {
return false;
}
return true;
}
}
/*
* Cache for multinomial calculation. All calculated multinomials are
* cached in order to improve algorithm's efficiency.
*/
private static final Map<Multinomial, BigInteger> cache =
new HashMap<Multinomial, BigInteger>();
/**
* @deprecated
* Calculates the
* <a href="https://en.wikipedia.org/wiki/Multinomial_coefficient#
* Multinomial_coefficients">multinomial coefficient.</a>
* <p>
* This function is recursive, so results are cached in order to avoid
* intensive computation of repetitive results. Unfortunately, some
* simple calculations may throw a {@link StackOverflowError} due to
* recursivity piling up too many calls to this function.
* </p>
* <p>
* The algorithm was inspired on Dave Barber's approach to
* <a href="http://home.comcast.net/~tamivox/dave/multinomial/index.html">
* calculate multinomials</a>.
* </p>
* <p>
* 17/04/2013<br>
* This method is not efficient and subject to stack overflow. Prefer
* {@link #multinomial(Multinomial)} to calculate multinomials.
* </p>
*
* @param m The multinomial to calculate
* @return The value of the specified multinomial
*/
static BigInteger calculate(Multinomial m) {
BigInteger result = BigInteger.ZERO;
if (m.size() == 0 || m.isZeroed()) {
result = BigInteger.ONE;
} else if (!m.isValid()) {
// returns 0 when invalid
} else if (cache.containsKey(m)) {
System.out.println("Retrieving value from cache for : " + m);
result = cache.get(m);
} else {
for (int i = 0; i < m.size(); i++) {
result = result.add(multinomial(m.decrement(i)));
}
cache.put(m, result);
}
return result;
}
/**
* Calculates the
* <a href="https://en.wikipedia.org/wiki/Multinomial_coefficient#
* Multinomial_coefficients">multinomial coefficient.</a>
*
* @param m The multinomial to calculate
* @return The value of the specified multinomial
*/
public static BigInteger multinomial(Multinomial m) {
BigInteger result = BigInteger.ONE;
if (m.size() < 2 || m.isZeroed()) {
result = BigInteger.ONE;
} else {
for (int i = 1; i < m.size(); i++) {
int n = m.sumTerms(0, i);
int k = m.get(i);
BigInteger c = combination(n, k);
result = result.multiply(c);
}
}
return result;
}
/**
* Returns the result of <code>b<sup>p/q</sup></code> as a
* {@link BigDecimal}.
* <p>
* This method is not defined for negative numbers, so <code>b</code>
* and <code>p/q</code> must be positive numbers. It throws a
* {@link IllegalArgumentException} when the exponent is negative or
* the base is negative.
* </p>
* <p>
* Exceptional cases:
* <li> 0<sup>n</sup> = 0 for n > 0
* <li> 0<sup>0</sup> = 1
* </p>
* <br>
*
* @param b The base
* @param p The numerator of the exponent
* @param q The denominator of the exponent
* @return the result of <code>b<sup>p/q</sup></code>
* @throws IllegalArgumentException if the exponet is negative or
* the base is negative.
*/
public static BigDecimal pow(BigDecimal b, int p, int q)
throws IllegalArgumentException {
BigDecimal result;
int sign = p * q;
if (b.signum() == 0) {
if (sign > 0) {
result = BigDecimal.ZERO;
} else if (sign == 0) {
result = BigDecimal.ONE;
} else {
throw new IllegalArgumentException("0^n, n < 0 is undefined!");
}
} else if (b.signum() < 0 || sign < 0) {
if (p % q == 0) {
int exp = p / q;
result = b.pow(exp, CONTEXT);
} else {
throw new IllegalArgumentException("Operation not defined for"
+ " negative numbers.");
}
} else {
// separates p/q in two parts: whole (i) and decimal (d)
int intPart = p / q;
double decPart = ((double) p) / q - intPart;
// calculates b^i using BigDecimal.pow()
BigDecimal intPow = b.pow(intPart, CONTEXT);
// calculates b^d using Math.pow()
double bAsDouble = b.doubleValue();
BigDecimal decPow = new BigDecimal(Math.pow(bAsDouble, decPart), CONTEXT);
result = intPow.multiply(decPow, CONTEXT);
}
calls++;
return result;
}
}