精确计算介于1和10之间的值的谐波数 我一直在C++中工作,作为项目的一部分,我需要计算实际值的谐波数。我有一个非常精确的公式来计算40以上的值,使用Kahan和我可以精确地得到一个整数的调和数,但是我不知道如何精确地计算像HarmonicNumber1.5这样的值。我该怎么做 注:如果任何人都有快速的C++代码来计算DigaMa函数,我也可以使用它,因为DigaMa函数很容易转换成调和数函数。

精确计算介于1和10之间的值的谐波数 我一直在C++中工作,作为项目的一部分,我需要计算实际值的谐波数。我有一个非常精确的公式来计算40以上的值,使用Kahan和我可以精确地得到一个整数的调和数,但是我不知道如何精确地计算像HarmonicNumber1.5这样的值。我该怎么做 注:如果任何人都有快速的C++代码来计算DigaMa函数,我也可以使用它,因为DigaMa函数很容易转换成调和数函数。,c++,math,optimization,numeric,C++,Math,Optimization,Numeric,编辑:我已经编写了以下代码,尽管我希望有更快、更简单的东西 编辑2:我需要一个相对误差小于10^-15的双精度值,小于10^-18的80位扩展精度值,如长双精度 long double HarmonicNumber(long double n) { //Absolute error is smaller than or equal to 2^-61, or 4.33681e-19, for n<5000 //For n>5000, all but the last two bits

编辑:我已经编写了以下代码,尽管我希望有更快、更简单的东西

编辑2:我需要一个相对误差小于10^-15的双精度值,小于10^-18的80位扩展精度值,如长双精度

long double HarmonicNumber(long double n)
{
//Absolute error is smaller than or equal to 2^-61, or 4.33681e-19, for n<5000
//For n>5000, all but the last two bits are correct. 
constexpr long double m1 = 1.0L / 24;
constexpr long double m2 = -7.0L / 960;
constexpr long double m3 = 31.0L / 8064;
constexpr long double m4 = -127.0L / 30720;
constexpr long double m5 = 511.0L / 67584;
constexpr long double m6 = -1414477.0L / 67092480;
constexpr long double m7 = 8191.0L / 98304;
constexpr long double EulerGamma = 0.5772156649015328606065120900824024310421L;
long double v = n + 0.5L;
long double v2 = 1.0L / (v * v);
//Uses asymptotic expansion with progressively more terms.
//Fewer terms are needed for larger inputs.
if(n >= 10000.L) return m1*v2 + log(v) + EulerGamma;
if(n >= 450.00L) return (m2*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 110.00L) return ((m3*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 42.000L) return (((m4*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 24.000L) return ((((m5*v2 + m4)*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 17.000L) return (((((m6*v2 + m5)*v2 + m4)*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 13.000L) return ((((((m7*v2 + m6)*v2 + m5)*v2 + m4)*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
if(n >= 6.0L)
{
    //Calculates HarmonicNumber(n+7) and then subtracts fraction to find HarmonicNumber(n)
    v = n + 7.5L;
    v2 = 1.0L / (v * v);
    auto base = ((((((m7*v2 + m6)*v2 + m5)*v2 + m4)*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
    long double n2 = n + 4.0L;
    auto n2sq = n2*n2;
    auto n2sh = n2sq - 7.0L;
    auto shft = 1.0L / n2 + (2*n2*n2sh*(3.0L*n2sq-7.0L))/(n2sq*n2sh*n2sh-36.0L);
    return base - shft;
}
else
{
    //Calculates HarmonicNumber(n+14) and then subtracts fraction to find HarmonicNumber(n)
    v = n + 14.5L;
    v2 = 1.0L / (v * v);
    auto base = ((((((m7*v2 + m6)*v2 + m5)*v2 + m4)*v2 + m3)*v2 + m2)*v2 + m1)*v2 + log(v) + EulerGamma;
    long double n2 = n + 4.0L;
    auto n2sq = n2*n2;
    auto n2sh = n2sq - 7.0L;
    auto shft = 1.0L / n2 + (2*n2*n2sh*(3.0L*n2sq-7.0L))/(n2sq*n2sh*n2sh-36.0L);
    n2 = n + 11.0L;
    n2sq = n2*n2;
    n2sh = n2sq - 7.0L;
    shft += 1.0L / n2 + (2*n2*n2sh*(3.0L*n2sq-7.0L))/(n2sq*n2sh*n2sh-36.0L);
    return base - shft;
}
}`

通常,函数被理解为仅为整数定义。正如您所指出的,您可以使用关系H_{n}=\psin+1+\gamma转换为函数,该函数是为非整数定义的。你可以称之为将调和数函数推广到非整数,但是如果你不想和你交谈的数学人搔搔头,交叉地看着你,你最好说你想计算digamma函数

因此,您需要计算digamma函数的大小值。幸运地我只在这里总结一下。对于大x,您需要使用伯努利数的渐近展开

对于所有x>~16,您可以使用存储的前12个伯努利数的表获得完全的双精度。对于较小的x,只需使用

将x移出到足够大的值以应用第一种技术。例如,对于x=12.5,只需求和s=1/12.5+1/13.5+1/14.5+1/15.5。然后通过前面的方法计算\psi16.5,并减去s得到\psi12.5

在我自己的例子中,我对小x使用了一种更为复杂的技术,速度更快,受抵消误差影响更小,但差别非常小。这种简单的技术对于大多数目的来说已经足够好了

最后,对于否定参数和非常接近零的参数,应该使用

转化为肯定论点

将所有这些放在一起,可以得到以下代码:

static const int bernoulli_length = 8;
static const double bernoulli[] = {
    1.0 / 6.0, -1.0 / 30.0, 1.0 / 42.0, -1.0 / 30.0,
    5.0 / 66.0, -691.0 / 2730.0, 7.0 / 6.0, -3617.0 / 510.0
};

double Psi(double x) {

    // Reflect to positive x
    if (x < 0.25) {
        // For x very close to a negative integer, this will loose accuracy
        // due to finite PI. To fix this, we need sinpi and cospi functions. 
        return (Psi(1.0 - x) - M_PI * cos(M_PI * x) / sin(M_PI * x));
    }

    // Shift out to large enough x
    double s = 0.0;
    while (x < 16.0) {
        s += 1.0 / x;
        x += 1.0;
    }

    // Use the asymptotic expansion
    double psi = log(x) - 1.0 / (2.0 * x);
    double x2 = x * x;
    double x2k = 1.0;
    for (int k = 0; k < bernoulli_length; k++) {
        double psi_old = psi;
        x2k *= x2;
        psi -= bernoulli[k] / (2 * (k + 1) * x2k);
        if (psi == psi_old) {
            return(psi - s);
        }
    }
    throw std::range_error("Convergence failure.");
}

我想说这更清楚一点。

请注意,与其他SE站点不同,MathJax在堆栈溢出方面不受支持。您可能需要将公式转换为图像。非常感谢您的回复。我已经阅读了维基百科关于调和数和Digamma函数的文章,有很多方法可以将调和数推广到实数。我上面的代码实际上依赖于渐近展开,只是不精确地依赖于伯努利展开,对于x小于14的值,我确实使用您描述的恒等式将其转换为x大于14的值-只是我将该步骤压缩为一个块,允许我将其移位7而不是1。我的代码是准确的,只是。。。难看。@Jorge:我已经为这种方法添加了代码,我认为它不那么麻烦和难看。你能确切地说一下你的计算需要有多精确吗?如果精度允许,你可以通过预先计算一个查找表并在其周围使用近似值来强制它。我希望它尽可能精确-最好至少是双精度。我看了一系列多项式插值的拼接,但那很麻烦,你能给问题增加精度要求吗?如果没有定义的要求,很难回答。如果输入大于1,则80位长的双精度码的相对误差小于10^-18,如果输入大于1,则常规双精度码的相对误差小于10^-15,然后将其添加到问题中。轻描淡写地说,这很重要