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

Avoiding Possible Precision Loss with a Simple Moving Average

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

Queue values;
double sum;

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

如果我们的 window 是 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)。那是必要的。所以,每次都重新计算是行不通的:window 太大了。

您可以重新计算每个 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;
}

如果你不断地为它提供邪恶的价值观,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 次左右就重置 window。这是因为,对于双精度,算法在大约 10^16 次加法后开始失去准确性。

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