避免在“简单求和”中使用double类型出现上溢/下溢问题

4

我遇到了一个求和问题,会因为上溢或下溢而失败。

我有超过8271571个双精度浮点数,需要求它们的算术平均值。

但主要问题是,我似乎不够聪明来解决这个问题。

目前,我只是将它们加起来然后除以数量。但这在大多数情况下会出现上溢或下溢,给我返回-1.#INF或1.#INF。

for(size_t j = 0; j < 12; j++)
{
    double a = 0.0;

    for(size_t i=0; i < Features->size(); i++)
    {
        a += Features->at(i)->at(j);
    }
    meanVector[j] = a / Features->size();
}

然而,无法确定其仅为正或负值,因此我不能将数据类型设置为有符号。

我还尝试在求和时使用除法常量或在添加它们时将其大小除以,但这也没有帮助。

从我快速查看的情况来看,这些值可能范围从-20到+30,但不能确定。

所以也许有人可以给我提示如何计算或使用替代方法。这一定是可行的,只是我缺乏想法。

编辑:

大小从未为0,在除法前进行了检查。 此外,任何值都不会以任何方式无效。在提取它们时,我已经检查了#IND和NaN。

如果我在求和时就除以了,那么这也不是正确的结果吗?

a+= Features->at(i)->at(j) / Features->size()

结果为-3.7964983860343639e + 305

但每次迭代都是这样的。这肯定不对,看起来像是边界问题。

编辑2:

所以你们中的一些人完全正确。有很多垃圾信息在发生...

0:大小:8327571,最小值:-2.24712e + 307,最大值:3362.12 1:大小:8327571,   最小值:-2.24712e + 307,最大值:142181 2:大小:8327571,最小值:-2.24712e + 307,   最大值:59537.8 3:大小:8327571,最小值:-2.24712e + 307,最大值:236815 4:   大小:8327571,最小值:-2.24712e + 307,最大值:353488 5:大小:8327571,最小值:   -2.24712e + 307,最大值:139960 6:大小:8327571,最小值:0,最大值:0 7:大小:8327571,最小值:0,最大值:0 8:大小:8327571,最小值:0,最大值:0 9:大小:   8327571,最小值:0,最大值:0 10:大小:8327571,最小值:0,最大值:0 11:大小:   8327571,最小值:0,最大值:0


2
你可以计算 number[i]/n 的总和 - 但这可能会增加数值误差... - amit
1
你确定 Features->size() 不为 0 吗?(提示:任何浮点数除以 0 都会得到无穷大 - 如果这个数是非负的,结果为 1.#INF,如果是负数,则为 -1.#INF - Kiril Kirov
也有可能被求和的值为+/-INF - 你应该添加一个断言来检查这个。 - Paul R
我编辑了一些你所要求的信息。 - Stefan
似乎不太可能,即使是从-20到30的size_t值的最大值也不可能达到double范围的末尾。我认为你的假设有误。 - James Clark
请发布一个完整的最小化示例以展示问题。 - Cheers and hth. - Alf
2个回答

4
我有超过8271571个双精度浮点数,需要计算它们的算术平均值。这些值可能范围很大,初步估计可能在-20到+30之间,但不能确定。我们会在除法前检查数据大小,所以不必担心出现0的情况。
这不太对劲。这个总和应该很容易适应双精度浮点数。可能是数据有问题。您可以使用以下代码快速检查数据:
for (size_t j = 0; j < 12; ++j)
{
    std::vector<double> values;

    values.reserve(Features->size());
    for (size_t i = 0; i < Features->size(); ++i)
    {
        values.push_back(Features->at(i)->at(j));
    }

    // Find extreme values, including infinity
    std::cout << j << ": " 
              << "size: " << values.size() 
              << ", min: " << *std::min_element(values.begin(), values.end())
              << ", max: " << *std::max_element(values.begin(), values.end())
              << std::endl;

    // Find NaNs
    for (size_t i = 0; i < Features->size(); ++i)
    {
        // Choose one of the following ifs

        // For C++11 (isnan is a standard thing now)
        if (std::isnan(Features->at(i)->at(j))

        // Or for Visual Studio
        if (_isnan(Features->at(i)->at(j))

        // Or for GCC prior to C++11
        if (__builtin_isnan(Features->at(i)->at(j))

        {
            std::cout << "NaN at [" << i << ", " << j << "]" << std::endl;
        }
    }
}

你应该能够快速发现输入是否存在任何异常。

1
+1. 这不对劲 - 从字面上和比喻上都是如此。某些数据似乎有问题。我怀疑一些值没有被初始化。这些数字可能代表某种物理量。无论是米、千克、人数还是其他什么,10^300的值都是垃圾值。 - David Hammen
+1 你们俩都是对的! 0: 大小:8327571,最小值:-2.24712e+307,最大值:3362.12 1: 大小:8327571,最小值:-2.24712e+307,最大值:142181 2: 大小:8327571,最小值:-2.24712e+307,最大值:59537.8 3: 大小:8327571,最小值:-2.24712e+307,最大值:236815 4: 大小:8327571,最小值:-2.24712e+307,最大值:353488 5: 大小:8327571,最小值:-2.24712e+307,最大值:139960 6: 大小:8327571,最小值:0,最大值:0 7: 大小:8327571,最小值:0,最大值:0 8: 大小:8327571,最小值:0,最大值:0 9: 大小:8327571,最小值:0,最大值:0 10: 大小:8327571,最小值:0,最大值:0 11: 大小:8327571,最小值:0,最大值:0 真是一堆垃圾狗屎,我真丢人! - Stefan
嗨,我现在已经“解决”了获取垃圾数据的问题。读取数据时有一个小错误...无论如何,我发现了一种奇怪的行为。使用循环检查元素运作良好,并给出了漂亮而平滑的数据。没有错误或垃圾值。但是如果稍后将它们加起来,我会发现向量中有一个值为-1.#IND..但是在将其推入循环到值时不是。直接使用已知索引访问它时,我再次看到#IND..疯狂吧? - Stefan
@Stefan,你的数据中有NaN。这不能被min/max_element检测到。我修改了代码以特别查找它们。如果你不知道NaN是什么,可以在这里阅读(http://en.wikipedia.org/wiki/NaN)。基本上它是一个非数字,就像`sqrt(-1)`的结果。 - detunized
是的,我知道NaN是什么,并使用value!= value进行检查以检测它们。但我不知道min/max无法检测到它们。谢谢你回来并让它更清晰 :-) - Stefan
1
@Stefan,value != value 并不总是起作用。编译器认为可以优化它们。 isnan 是你的朋友。min_element 无法检测它们,因为它们既不 < 也不 > 于任何其他元素。在某些特殊情况下,你可以通过 min_element 返回它们,但这并非普遍情况。 - detunized

0

您可以使用在线算法计算平均值,这意味着在除法之前您不必将所有值相加。如下:

template< typename NumberType >
class ProgressiveMean{
    NumberType  m_Mean;
    NumberType  m_MeanKMinus1;
    long        m_K;
public:
    ProgressiveMean();
    void Seed( NumberType seed );
    void AddValue( NumberType newVal );
    NumberType getMean() const;
};

template< typename NumberType >
ProgressiveMean<NumberType>::ProgressiveMean():
    m_Mean( 0 ),
    m_MeanKMinus1( 0 ),
    m_K( 0 ){
}

template< typename NumberType >
void ProgressiveMean<NumberType>::Seed( NumberType seed ){
    m_MeanKMinus1 = seed
    m_K = 2;  //Start from K = 1, so next one is 2
}

template< typename NumberType >
void ProgressiveMean<NumberType>::AddValue( NumberType newVal ){
    m_Mean = m_MeanKMinus1 + (newVal - m_MeanKMinus1) / m_K;
    m_MeanKMinus1 = m_Mean;
    m_K++;
}

template< typename NumberType >
NumberType ProgressiveMean<NumberType>::getMean() const{
    return m_Mean;
}

要使用它,调用Seed并传入初始值,循环调用AddValue添加剩余的值,完成后调用getMean

这个想法来自于Knuth,我从这里得到了它。

您还可以考虑使用大数库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接