使用简单移动平均线避免可能的精度损失
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 更复杂,所以它可能更慢。
假设我们有一个跟踪总和的基本移动平均函数。例如:
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 更复杂,所以它可能更慢。