如何汇总大量浮点数?

时间:2010-01-27 15:32:33

标签: c++

我建立一个并行和代码来汇总大量的浮点数然后我发现当数字的数量大于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;
}

5 个答案:

答案 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}},请尝试以下操作:

  • 将您的数字从最负面到最正面排序(顺序(N log N))
  • 依次添加每一对:B1:= A1 + A2,B2:= A3 + A4,B3:= A5 + A6 这会产生一个新的列表B,是初始列表的一半
  • 在B上重复此过程以获得C,C以获得D等
  • 只剩下一个号码时停止。

请注意,这会将您的算法复杂度从O(N)操作更改为O(N log N)操作,但更有可能产生正确的数字。它非常平行。如果你聪明的话,你可以合并排序和求和操作。

答案 1 :(得分:9)

使用Kahan summation algorithm

  • 它具有与天真求和相同的算法复杂度
  • 它将大大提高求和的准确性,而无需将数据类型切换为加倍。

我使用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;
}

你当然可以切换到所有floatsdoubles,并且kahan求和算法会比使用双精度的朴素总和更精确。

答案 2 :(得分:3)

IEEE754不准确。试试GMP

答案 3 :(得分:2)

如果您要求的数字在数量上有很大差异,您可以先对它们进行排序,然后从最小的数字开始添加它们。这将确保他们的结果仍然可以计算,并且不会因精度问题而丢失(这也存在double,只需要更长的时间来显示)。

否则,你可以建立一些部分和,每个部分的大小大致相同,然后将它们加在一起;这也应该确保你得到正确的结果。

或者为任意精度数学寻找一个很好的库:)

答案 4 :(得分:-1)

Alex Brown的回答很棒。 。有关浮点问题的完整说明,请尝试this