如何编写一个计算带小数阶乘的函数?

10

如何在JavaScript中编写一个可以计算十进制数阶乘(或伽马函数)的函数?例如,我应该如何计算2.33!


你尝试过什么?至少有一个可能是重复的:https://dev59.com/NW865IYBdhLWcg3wKLNP - Rubens Mariuzzo
1
函数fact(n) { var x = 1 for (var i = 2; i <= n; i++) { x = x * i } 返回x } 但是这个函数无法计算带小数的阶乘,我不知道如何为其创建伽玛函数。 - Mich'
1
我发现之前有一个答案,但它并不完全正确。 - Mich'
你是正确的 @Mich,点赞。 - Rubens Mariuzzo
常量数组似乎不正确。 - Mich'
这并不是一件简单的事情,那么你的背景是什么(你是否阅读并理解了http://en.wikipedia.org/wiki/Gamma_function)?你在JavaScript中需要这个东西做什么? - Bergi
6个回答

13
我可能已经找到了一个现成的解决方案......它是Lanczos方法的实现,我在瑞典维基百科(http://sv.wikipedia.org/wiki/Gammafunktionen)中找到了它。该实现使用Python编写,声称正确性可达15个小数位。我将其移植到JS,并对一些随机值进行了交叉验证(http://www.efunda.com/math/gamma/findgamma.cfm)。 http://jsfiddle.net/Fzy9C/
var g = 7;
var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];

function gamma(z) {

    if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
    else {
        z -= 1;

        var x = C[0];
        for (var i = 1; i < g + 2; i++)
        x += C[i] / (z + i);

        var t = z + g + 0.5;
        return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
    }
}

当然,它不支持虚数,因为js不支持。


7
作为这里其他答案的替代方案,这里提供了一个更简单的伽马函数近似值,由Gergő Nemes于2007年提出(参见斯特林公式的维基百科页面)。

enter image description here

这可以直接在JavaScript中用一行代码实现:

function gamma(z) {
  return Math.sqrt(2 * Math.PI / z) * Math.pow((1 / Math.E) * (z + 1 / (12 * z - 1 / (10 * z))), z);
}

您可以在jsFiddle上看到此操作的实例。

对于z>8,此方法可精确到8位,但对于较小的z,仅能准确保留几位数字。它的准确度不如Lanczos逼近法,但更简单,也稍微快一些

请注意伽马函数和阶乘函数略有不同。阶乘函数可以用伽马函数来定义:

function factorial(n) {
  return gamma(n + 1);
}

这里有什么不正确的地方吗?我想知道为什么会被踩。 - Peter Olson
@PeterOhlson 我不确定,但是当我在jsfiddle中执行factorial(2.44)时,我得到的是3.11...而不是1.27...。我也没有得到其他数字返回正确的值...对于我的投票抱歉,我投了赞成票,但遇到了这个问题,如果我看到它正常工作,我会再次投赞成票。看起来是一个很好的解决方案。 - apelsinapa
@apelsinapa 3.11是正确答案。1.27是阶乘(1.44),而不是阶乘(2.44)。 - Peter Olson
@PeterOhlson 不符合以下网站的计算结果:http://www.danielsoper.com/statcalc3/calc.aspx?id=30,http://www.efunda.com/math/gamma/findgamma.cfm和http://www.miniwebtool.com/gamma-function-calculator/?number=2.44? - apelsinapa
@apelsinapa 这是因为这是伽玛函数,而jsFiddle是阶乘函数。它们略有不同。在jsFiddle页面上,您将看到伽玛函数定义在阶乘函数内部。如果您愿意,这里是单独的伽玛函数 - Peter Olson
@PeterOhlson 哈哈,抱歉我觉得可能是这瓶啤酒的原因 =) 顺便说一下,这是一个更加优雅的解决方案。 - apelsinapa

4
这不是一个简单的问题。Gamma函数没有一个简单的闭式公式。尽管如此,有一些数值近似可以满足您的需求。
下面的答案将使用一种称为Lanczos近似的技术。公式如下:

enter image description here

其中g是任意选择的常数,控制着逼近的精度。g越大,逼近的精度就越高。Ag(z)的定义如下:

enter image description here

最困难的部分是找到Ag(z),因为pn也是使用一个依赖于g的复杂公式定义的

以下代码我不能太拿credit,因为我只是在写维基百科页面上Python程序的移植。

function gamma(n) {  // accurate to about 15 decimal places
  //some magic constants 
  var g = 7, // g represents the precision desired, p is the values of p[i] to plug into Lanczos' formula
      p = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
  if(n < 0.5) {
    return Math.PI / Math.sin(n * Math.PI) / gamma(1 - n);
  }
  else {
    n--;
    var x = p[0];
    for(var i = 1; i < g + 2; i++) {
      x += p[i] / (n + i);
    }
    var t = n + g + 0.5;
    return Math.sqrt(2 * Math.PI) * Math.pow(t, (n + 0.5)) * Math.exp(-t) * x;
  }
}

当然,根据伽玛函数的定义:

function factorial(n) {
  return gamma(n + 1);
}

你可以在 jsFiddle 上 查看实际效果


2

为了完善@apelsinapa的答案,修正整数计算(当输入整数时,我们没有得到整数解)。

@apelsinapa的好方法:

var g = 7;
var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028,771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];

function gamma(z) {
    if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
    else {
        z -= 1;

        var x = C[0];
        for (var i = 1; i < g + 2; i++)
        x += C[i] / (z + i);

        var t = z + g + 0.5;
        return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
    }
}

如果要得到一个整数的正确答案:

 function factorialOfNumber(number) {
    if (number % 1 != 0 || number<0){
        return gamma(number + 1);
    }
    else {
        if(number == 0) {
           return 1;
        }
        for(var i = number; --i; ) {
           number *= i;
        }
        return number;
    }
}     

0

这是我几年前写的一个版本...有点乱但经过测试 :)

var
    M_GAMMA = [76.18009172947146, -86.50532032941677, 24.01409824083091, -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5],
    M_GAMMAS = 6;

function gammaFn(x) // Modified to JavaScript from "Numerical Recipies in C" by P. Mainwaring
{
    var i = 0, n = ++x, tmp = x + 5.5, ser = 1.000000000190015;
    for (tmp -= (x + 0.5) * Math.log(tmp); i < M_GAMMAS; ++i) ser += M_GAMMA [i] / ++n;
    return Math.log(2.5066282746310005 * ser / x) - tmp;
}

function fact(x) { return x > 1 ? Math.exp(gammaFn(x)) : 1 }

function combin(n, k) { return (Math.exp(gammaFn(n) - gammaFn(n - k) - gammaFn(k)) + 0.5) | 0 } // Ms Excel COMBIN() n! / k!(n - k)!

n = 49; k = 6; alert(fact(n) + ' ' + fact(k) + ' ' + combin(n, k)); // Lottery odds! (13,983,816)

Gamma函数和GammaLn函数如下:

function gammaLn(x) { return gammaFn(--x) }

function gamma(x) { return Math.exp(gammaLn(x)) }

:-)


0

如果你只是想要计算实数阶乘的函数,那么你只需要使用Lanczos逼近中的这段代码:

function = factorial(z) {

  var g = 7;
  var C = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7];
  var x = C[0];

  for (var i = 1; i < g + 2; i++)
  x += C[i] / (z + i);

  var t = z + g + 0.5;
  return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
}

对于负数和小数的加法运算都非常有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接