2015-03-04 13:53:32 -05:00

137 lines
3.5 KiB
JavaScript

'use strict';
module.exports = function (math, config) {
var util = require('../../util/index'),
BigNumber = math.type.BigNumber,
collection = math.collection,
isNumber = util.number.isNumber,
isBoolean = util['boolean'].isBoolean,
isInteger = util.number.isInteger,
isCollection = collection.isCollection;
/**
* Compute the factorial of a value
*
* Factorial only supports an integer value as argument.
* For matrices, the function is evaluated element wise.
*
* Syntax:
*
* math.factorial(n)
*
* Examples:
*
* math.factorial(5); // returns 120
* math.factorial(3); // returns 6
*
* See also:
*
* combinations, gamma, permutations
*
* @param {Number | BigNumber | Array | Matrix | Boolean | null} n An integer number
* @return {Number | BigNumber | Array | Matrix} The factorial of `n`
*/
math.factorial = function factorial (n) {
var value, res, preciseFacs;
if (arguments.length != 1) {
throw new math.error.ArgumentsError('factorial', arguments.length, 1);
}
if (isNumber(n)) {
return n !== Number.POSITIVE_INFINITY
? math.gamma(n + 1)
: Math.sqrt(2*Math.PI);
}
if (n instanceof BigNumber) {
if (!(isNonNegativeInteger(n))) {
return n.isNegative() || n.isFinite()
? math.gamma(n.plus(1))
: util.bignumber.tau(config.precision).sqrt();
}
n = n.toNumber(); // should definitely be below Number.MAX_VALUE
if (n < smallBigFacs.length) {
return BigNumber.convert(smallBigFacs[n]).toSD(config.precision);
}
// be wary of round-off errors
var precision = config.precision + (Math.log(n) | 0);
var Big = BigNumber.constructor({precision: precision});
// adjust n do align with the precision specific tables
n -= smallBigFacs.length;
if (preciseFacs = bigBigFacs[precision]) {
if (preciseFacs[n]) {
return new BigNumber(preciseFacs[n].toPrecision(config.precision));
}
res = preciseFacs[preciseFacs.length-1];
} else {
preciseFacs = bigBigFacs[precision] = [];
res = new Big(smallBigFacs[smallBigFacs.length-1])
.toSD(precision);
}
var one = new Big(1);
value = new Big(preciseFacs.length + smallBigFacs.length);
for (var i = preciseFacs.length; i < n; ++i) {
preciseFacs[i] = res = res.times(value);
value = value.plus(one);
}
preciseFacs[n] = res.times(value);
return new BigNumber(preciseFacs[n].toPrecision(config.precision));
}
if (isBoolean(n) || n === null) {
return 1; // factorial(1) = 1, factorial(0) = 1
}
if (isCollection(n)) {
return collection.deepMap(n, factorial);
}
throw new math.error.UnsupportedTypeError('factorial', math['typeof'](n));
};
/**
* Test whether BigNumber n is a non-negative integer
* @param {BigNumber} n
* @returns {boolean} isNonNegativeInteger
*/
var isNonNegativeInteger = function(n) {
return n.isInteger() && (!n.isNegative() || n.isZero());
};
// 21! >= values for each precision
var bigBigFacs = [];
// 0-20! values
var smallBigFacs = [
1,
1,
2,
6,
24,
120,
720,
5040,
40320,
362880,
3628800,
39916800,
479001600,
6227020800,
87178291200,
1307674368000,
20922789888000,
355687428096000,
6402373705728000,
121645100408832000,
2432902008176640000
]
};