我在Manjaro 64位,最新版本。 HP pavilion g6,Codeblocks 版本13.12 rev 9501(2013-12-25 18:25:45)gcc 5.2.0 Linux / unicode - 64位。
学生们讨论了为什么
所以决定写一个关于它的程序,只是为了向他们展示他们可以期待什么样的输出
#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。同样的结果也是双倍。
答案 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);