减少浮点平方和的舍入误差

时间:2017-02-17 23:06:07

标签: c++ numerical-methods numerical

我尝试计算浮点数组的平方和。

如何减少舍入误差?

我想在实际程序的内循环中总计大约5,000,000个浮点数。

TEST.CPP:

#include <iostream>
#include <stdint.h>
template <typename Sum, typename Element>
Sum sum(const size_t st, const size_t en) {
    Sum s = 0;
    for (size_t i = st; i < en; ++ i) {
        s += Element(i)*Element(i); 
    }
    return s;
}
int main() {
    size_t size = 100000;
    std::cout << "double, float: " 
              << sum<double, float>(0,size) << "\n";
    std::cout << "int, int: " 
              << sum<int, int>(0,size) << "\n";
}

输出:

double, float: 3.33328e+14
int, int: 216474736

4 个答案:

答案 0 :(得分:2)

如果已知浮点数的格式,例如IEEE,则​​可以使用由浮点数的指数索引的数组来存储部分和,然后求和以生成总和。在阵列更新期间,只有具有相同指数的浮点数一起添加并存储到适当位置的数组中。最终总和从最小到最大。对于C ++,数组和函数可以是类的成员。

将数组作为参数传递给函数的浮点数示例:

/* clear array */
void clearsum(float asum[256])
{
size_t i;
    for(i = 0; i < 256; i++)
        asum[i] = 0.f;
}

/* add a number into array */
void addtosum(float f, float asum[256])
{
size_t i;
    while(1){
        /* i = exponent of f */
        i = ((size_t)((*(unsigned int *)&f)>>23))&0xff;
        if(i == 0xff){          /* max exponent, could be overflow */
            asum[i] += f;
            return;
        }
        if(asum[i] == 0.f){     /* if empty slot store f */
            asum[i] = f;
            return;
        }
        f += asum[i];           /* else add slot to f, clear slot */
        asum[i] = 0.f;          /* and continue until empty slot */
    }
}

/* return sum from array */
float returnsum(float asum[256])
{
float sum = 0.f;
size_t i;
    for(i = 0; i < 256; i++)
        sum += asum[i];
    return sum;
}

答案 1 :(得分:2)

int使用Element类型时,i之后的每个std::sqrt(std::numeric_limits<int>::max())广场都会出现溢出,可能是46341你的系统。 当总和到达std::numeric_limits<int>::max()时,总和上也有溢出。

您可以使用longlong long代替int来增加此数字。

在乘法之前将第一个float存储或转换为doublelong double也是一个好主意,以减少浮点平方操作的误差。 舍入一组计算的最后几步总是比舍入早期步骤更好,因为您避免在内部计算上传播(并增加)表示错误。

如果你真的想要精确,并且不想使用一些复杂的技术重新发明轮子,你可以使用像GNU Multi-Precision LibraryBoost Multiprecision这样的多精度库: https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software

它们比您系统的long double类型更准确

答案 2 :(得分:2)

如果您只想添加连续值的方块,请使用公式n*(n+1)*(2n+1)/6计算从1n的所有值的平方和。

只要使用可以表示结果的类型,就可以消除舍入的大多数影响。例如;

 template<typename Sum> Sum sumsq(size_t n)
 {
     // calculates sum of squares from 1 to x
     //   assumes size_t can be promoted to a Sum

     Sum temp(n);     // force promotion to type Sum
     return temp * (temp + 1)* (2*temp + 1)/6;
 }

 template<typename Sum> Sum alternate_sum(size_t st, size_t en)
 {
        Sum result = sumsq(en - 1);
        if (st > 0) result -= sumsq(st-1);
        return result;
 }

 int main()
 {
     size_t size = 100000;
     std::cout << "double, float: " 
              << alternate_sum<double>(0,size) << "\n";
     std::cout << "int, int: " 
          << alternate_sum<long long>(0,size) << "\n";
 }

请注意,对于size等于100000,使用int保存结果会产生未定义的行为(有符号整数类型的溢出)。

-1中的alternate_sum()反映您的循环格式为for (size_t i = st; i < en; ++ i)

您可以取消使用size_t类型作为固定功能,但我会将其作为练习。

BTW:既然你说这段代码是在内循环中,那么值得注意的是,这个公式将比你一直使用的循环快得多。

答案 3 :(得分:2)

一个浮点数有24个有效位,而一个浮点数有53个。所以你有29个保护位,大约是5000000的100倍。

因此,只有比最大值小100倍的值才会出现舍入误差。

另请注意,在英特尔架构中,浮点寄存器实际上保持80位的扩展精度数,其中63位是重要的。

然后,只有小于最大值100000倍的数字才会发生截断。

你真的担心吗?