我建立一个并行和代码来汇总大量的浮点数然后我发现当数字的数量大于100000000时,结果会出错。然后我构建一个串行代码进行比较。序列号也输错了号码。谁知道为什么?谢谢!
我的简单代码如下。
结果是“1.67772e + 007”。它应该是1e + 008
int main()
{
size_t N=100000000;
cout<<"n is : "<<N<<endl;
clock_t start = clock();
task_scheduler_init init;
vector<float> myvec;
vector<float>* pvec;
for(int i=0;i<N;i++)
myvec.push_back(1.0f);
pvec=&myvec;
float mysum;
mysum=parallelSum(pvec);
cout<<" the p sum is: "<<mysum<<endl;
clock_t finish = clock();
cout<<"Time Used = "<<(finish - start)/CLOCKS_PER_SEC<<endl;
mysum=0;
for(int i=0;i<N;i++)
mysum+=myvec[i];
cout<<" the s sum is: "<<mysum<<endl;
return 0;
}
答案 0 :(得分:28)
您的问题是由于浮点数的可用精度有限。
虽然
1.0f + 1.0f == 2.0f,
你会发现
16777216.0f + 1.0f == 16777216.0f
额外的1.0f被丢弃,因为16777217无法以float
格式表示。
查看你的结果--1.67772e + 007 - 很明显,这正是发生的事情。
您的预期结果,100000000.0,比16777216.0f大很多(6x),但是一旦总和达到16777216.0f,它就会留在那里,剩余的8327884加上1.0f。
解决方案:尝试使用double
代替float
,在遇到此问题之前,该9007199254740992.0
最多可达float
。
在单精度浮点中,只有24位精度可用,2 ^ 24是16777216.没有办法用24位编码16777217,所以它只是保持在16777216,因为它是足够接近到真正的答案。当你向一个大数字添加许多非常小的数字时会产生真正的问题,其中小数字的总和相对于大数字是显着的,但是它们不是单独的。
注意16777216.0f不是 可以表示的最大数字 在
double
,但只代表 精度极限。例如, 16777216.0f x 2 ^ 4 + 2 ^ 4 =&gt; 16777216.0f x 2 ^ 4
9007199254740992.0
具有53位精度,因此可以在达到添加1.0d
失败的点之前编码最多2 ^ 53或Sum(A) = (...((((A1 + A2) + A3) + A4) ... A10000)
。
这个问题也代表了浮点运算并行化的另一个危险 - 浮点加法是非关联,换句话说,你的顺序算法:
Sum(A) = (...((((A1 + A2) + A3) + A4) ... A1000)
+ (...((((A1001 + A1002) + A1003) + A1004) ... A2000)
+ (...((((A2001 + A2002) + A2003) + A2004) ... A3000)
...
+ (...((((A9001 + A9002) + A9003) + A9004) ... A10000)
可能会产生与并行版本不同的结果:
float
因为任何给定的步骤都可能在不同程度上失去精确度。
这并不意味着更正确,但您可能会得到意想不到的结果。
如果您真的必须使用{{1}},请尝试以下操作:
请注意,这会将您的算法复杂度从O(N)操作更改为O(N log N)操作,但更有可能产生正确的数字。它非常平行。如果你聪明的话,你可以合并排序和求和操作。
答案 1 :(得分:9)
我使用std :: accumulate测试了总计1000000个1.0f的浮点数 - 结果是1.67772e + 007。但是,使用此Kahan求和实现,结果为1e + 008
template <class Iter>
float kahan_summation(Iter begin, Iter end) {
float result = 0.f;
float c = 0.f;
for(;begin != end; ++begin) {
float y = *begin - c;
float t = result + y;
c = (t - result) - y;
result = t;
}
return result;
}
你当然可以切换到所有floats
到doubles
,并且kahan求和算法会比使用双精度的朴素总和更精确。
答案 2 :(得分:3)
IEEE754不准确。试试GMP。
答案 3 :(得分:2)
如果您要求的数字在数量上有很大差异,您可以先对它们进行排序,然后从最小的数字开始添加它们。这将确保他们的结果仍然可以计算,并且不会因精度问题而丢失(这也存在double
,只需要更长的时间来显示)。
否则,你可以建立一些部分和,每个部分的大小大致相同,然后将它们加在一起;这也应该确保你得到正确的结果。
或者为任意精度数学寻找一个很好的库:)
答案 4 :(得分:-1)