当数字变得那么小时,它会使数字翻倍吗?

时间:2015-11-24 19:51:07

标签: c++ gcc

我在Manjaro 64位,最新版本。 HP pavilion g6,Codeblocks 版本13.12 rev 9501(2013-12-25 18:25:45)gcc 5.2.0 Linux / unicode - 64位。

学生们讨论了为什么

  • sn = 1 / n diverges
  • sn = 1 / n ^ 2收敛

所以决定写一个关于它的程序,只是为了向他们展示他们可以期待什么样的输出

#include <iostream>
#include <math.h>
#include <fstream>

using namespace std;

int main()
{
   long double sn =0, sn2=0; // sn2 is 1/n^2
   ofstream myfile;
   myfile.open("/home/Projects/c++/test/test.csv");

    for (double n =2; n<100000000;n++){
            sn += 1/n;
            sn2 += 1/pow(n,2);
            myfile << "For n = " << n << " Sn = " << sn << " and Sn2 = " << sn2 << endl;

    }

    myfile.close();
    return 0;
}   

从n = 9944开始,我得到sn2 = 0.644834,并且永远得到它。我确实期望编译器将数字四舍五入并在某个时候忽略0,但这太早了,不是吗?

那么在什么理论点上0开始被忽略?如果你关心一个数字中的所有0,该怎么办?如果长双不做,那又怎样呢?

我知道这似乎是一个愚蠢的问题,但我希望看到更长的数字,因为你可以在长双打中存储大部分的pi。同样的结果也是双倍。

2 个答案:

答案 0 :(得分:7)

您编写的代码遇到了一个经典的编程错误:它通过将较大的数字加到第一个和较小的数字来加总一系列浮点数。

这将不可避免地导致在加法期间的精度损失,因为在序列中的某一点,总和将变得相对较大,而序列的下一个成员将变得相对较小。将足够小的浮点值添加到足够大的浮点和不会影响总和。一旦达到该点,即使您尝试添加的值不为零,也会看起来“添加”操作被“忽略”。

如果您尝试在典型计算机上计算100000000.0f + 1,则可以观察到相同的效果:它仍然评估为100000000。这不会发生,因为1以某种方式舍入为零。这是因为数学上正确的结果100000001舍入回到100000000。为了强制100000000.0f通过添加进行更改,您需要至少添加5(结果将被“捕捉”到100000008)。

所以,这里的问题并不是编译器“在它变得如此之小时将数字舍入”,正如你似乎相信的那样。您的1/pow(n,2)号码可能很好且足够精确(未舍入为0)。这里的问题是,在你的周期的某个迭代中,1/pow(n,2)的小的非零值不再影响总和。

虽然调整输出精度确实可以帮助您更好地了解正在发生的事情(如评论中所述),但真正的问题是上述内容。

当计算成员幅度差异较大的浮点序列的总和时,您应该首先添加序列中较小的成员。再次使用我的100000000.0f示例,您可以轻松地看到4.0f + 4.0f + 100000000.0f正确生成100000008,而100000000.0f + 4.0f + 4.0f仍然是100000000

答案 1 :(得分:2)

这里没有遇到精确问题。总和不会停在0.644834; it keeps going to roughly the correct value

#include <iostream>
#include <math.h>
using namespace std;

int main() {
    long double d = 0;
    for (double n = 2; n < 100000000; n++) {
        d += 1/pow(n, 2);
    }
    std::cout << d << endl;
    return 0;
}

结果:

0.644934

注意9!那不再是0.644834了。

如果您期望1.644934,则应该在n=1处开始计算金额。如果您期望连续的部分和之间有明显的变化,那么您没有看到它们,因为C ++正在将总和的表示截断为6位有效数字。您可以将输出流配置为使用std::setprecision标题中的iomanip显示更多数字:

myfile << std::setprecision(9);