在 C++ 中除数时如何获得更准确的结果

How can I get a more accurate result when dividing numbers in C++

我正在尝试使用 C++ 估算 PI 作为一个有趣的数学项目。我 运行 遇到一个问题,我只能将其精确到小数点后 6 位。

我尝试使用浮点数而不是双精度数,但发现了相同的结果。

我的代码通过将 1/n^2 的所有结果相加来工作,其中 n=1 到定义的限制。然后它将此结果乘以 6 并取平方根。 Here is a link to an image written out in mathematical notation

这是我的 main 函数。 PREC 是预定义的限制。它将用这些分数的结果填充数组并得到总和。我的猜测是 sqrt 函数导致我无法获得比 6 位数更精确的问题。

int main(int argc, char *argv[]) {
    nthsums = new float[PREC];

    for (int i = 1; i < PREC + 1; i += 1) {
        nthsums[i] = nth_fraction(i);
    }

    float array_sum = sum_array(nthsums);
    array_sum *= 6.000000D;

    float result = sqrt(array_sum);
    std::string resultString = std::to_string(result);

    cout << resultString << "\n";
}

为了这个目的,我还将包括我的求和函数,因为我怀疑它也可能有问题。

float sum_array(float *array) {
    float returnSum = 0;
    for (int itter = 0; itter < PREC + 1; itter += 1) {
        if (array[itter] >= 0) {
            returnSum += array[itter];
        }
    }
    
    return returnSum;
}

我希望至少精确到 10 位数字。有没有办法在 C++ 中做到这一点?

因此,即使使用 long double 作为用于此的浮点类型,也需要一些微妙之处,因为添加两个数量级大不相同的 long double 会导致精度损失。请参阅 here 以了解 Java 中的讨论,但我相信它在 C++ 中的行为基本相同。

我使用的代码:

#include <iostream>

#include <cmath>
#include <numbers>

long double pSeriesApprox(unsigned long long t_terms)
{
    long double pi_squared = 0.L;
    for (unsigned long long i = t_terms; i >= 1; --i)
    {
        pi_squared += 6.L * (1.L / i) * (1.L / i);
    }
    return std::sqrtl(pi_squared);
}

int main(int, char[]) {
    const long double pi = std::numbers::pi_v<long double>;
    const unsigned long long num_terms = 10'000'000'000;

    std::cout.precision(30);
    std::cout << "Pi == " << pi << "\n\n";
    std::cout << "Pi ~= " << pSeriesApprox(num_terms) << " after " << num_terms << " terms\n";

    return 0;
}

输出:

Pi == 3.14159265358979311599796346854
Pi ~= 3.14159265349430016911469465413 after 10000000000 terms

9 位小数的准确度,这大约是我们对以此速率收敛的序列的期望值。

但如果我所做的只是颠倒 pSeriesApprox 中循环的顺序,添加完全相同的项,但从最大到最小而不是从最小到最大:

long double pSeriesApprox(unsigned long long t_terms)
{
    long double pi_squared = 0.L;
    for (unsigned long long i = 1; i <= t_terms; ++i)
    {
        pi_squared += 6.L * (1.L / i) * (1.L / i);
    }
    return std::sqrtl(pi_squared);
}

输出:

Pi == 3.14159265358979311599796346854
Pi ~= 3.14159264365071688729358356795 after 10000000000 terms

尽管我们使用了 100 亿个术语,但我们的准确性突然下降到了 7 位数。事实上,在大约 1 亿项之后,pi 的近似值稳定在这个特定值。因此,虽然使用足够大的数据类型来存储这些计算很重要,但在尝试执行此类求和时仍需要额外注意。