使用简单移动平均线避免可能的精度损失

时间:2015-07-08 23:59:15

标签: algorithm floating-point-precision moving-average

假设我们有一个跟踪总和的基本移动平均函数。例如:

Queue values;
double sum;

double CalcSMA(double next) {
    values.push(next);
    sum -= values.pop();
    sum += next;
    return sum / SMA_LENGTH;
}

如果我们的窗口宽度为5,那么这可能会导致崩溃的一个例子就是: 2, 2, 2, 2, 2, 2, 2, 1E100, 2, 2, 2, 2, 2, 2, 2, 2。 然后输出为2, 2, 2, 2E99, 2E99, 2E99, 2E99, 2E99, 0, 0, 0

即使总和并没有那么显着,但仍然可能有一些非常合理的例子,精确度的微小损失可能会使人为增加一小部分。在很长一段时间内,这可能会成为一个问题。

有没有人对如何解决精度损失有任何想法?

编辑:请注意,此功能旨在用于工作O(1)。这是必要的。因此,每次重新计算都不会起作用:窗口太大了。

2 个答案:

答案 0 :(得分:4)

您可以重新计算每个SMA_LENGTH值的新总和,以阻止错误累积:

Queue values;
double sum = 0.0;
double freshsum = 0.0;
int count = 0;

double CalcSMA(double next) {
    values.push(next);
    sum -= values.pop();
    sum += next;
    freshsum += next;
    if(++count == SMA_LENGTH)
    {
        sum = freshsum;
        freshsum = 0.0;
        count = 0;
    } 
    return sum / SMA_LENGTH;
}

答案 1 :(得分:1)

如果你不断向它提供邪恶的价值,samgak提出的实际上并不能保证良好的平均值。

您可以使用Neumaier's算法在O(1)时间内生成准确的结果。像这样:

const double SMA_LENGTH = 5;
Queue values;
double sum = 0.0;
double correction = 0.0;

static void Neumaier(double value, ref double sum, ref double correction)
{
    var t = sum + value;
    if (Math.Abs(sum) >= Math.Abs(value))
        correction += (sum - t) + value;
    else
        correction += (value - t) + sum;
    sum = t;
}

double CalcSMA(double next)
{
    Neumaier(-values.pop(), ref sum, ref correction);
    Neumaier(next, ref sum, ref correction);
    values.push(next);
    return (sum + correction) / SMA_LENGTH;
}

如果您有大量序列,则可以每隔10 ^ 15左右重新添加窗口。这是因为,在双精度之后,算法在大约10 ^ 16次加法后开始失去准确性。

另一方面,Neumaier更复杂,所以它可能更慢。