基本的に、データサンプルとして最新の1000の数値を使用して、浮動小数点数のストリームの進行中のストリームの移動平均を追跡したいと思います。
total_
以下は、追加/置換されたas要素を更新し、オンデマンドで合計(平均に必要)を計算するためのコストのかかるO (N)トラバーサルを回避することに注意してください。
template <typename T, typename Total, size_t N>
class Moving_Average
{
public:
Moving_Average& operator()(T sample)
{
total_ += sample;
if (num_samples_ < N)
samples_[num_samples_++] = sample;
else
{
T& oldest = samples_[num_samples_++ % N];
total_ -= oldest;
oldest = sample;
}
return *this;
}
operator double() const { return total_ / std::min(num_samples_, N); }
private:
T samples_[N];
size_t num_samples_{0};
Total total_{0};
};
例:
// average of last 3 (from 4) samples...
std::cout << Moving_Average<double, double, 3>{}(4)(7)(2)(6) << '\n';
// "5\n"
// average of last 3 squares...
Moving_Average<double, double, 3> ma;
for (int i = 0; i < 10; ++i)
std::cout << (i * i) << ':' << ma(i * i) << ' ';
std::cout << '\n';
// 0:0 1:0.5 4:1.66667 9:4.66667 16:9.66667 25:16.6667 36:25.6667 49:36.6667 64:49.6667 81:64.6667
Total
T
は、をサポートするためとは異なるパラメータになります。たとえば、合計long long
が1000long
秒の場合、int
for char
s、またはa tototalsを使用します。double
float
問題
num_samples_
これは、概念的に0に戻る可能性があるという点で少し欠陥がありますが、 2 ^ 64のサンプルを持っている人を想像するのは難しいです。懸念がある場合は、追加のデータメンバーを使用して、配列bool
を循環しているときにコンテナが最初に満たされたときを記録します( num_samples_
「」のように無害な名前に変更しましpos
た。
T=double
もう1つの問題は浮動小数点の精度に固有のものであり、の簡単なシナリオで説明できますN=2
。:から始めてtotal_ = 0
、サンプルを注入します{1E17, 1, 2}
...
1E17、実行するtotal_ += 1E17
のでtotal_ == 1E17
、注入します
1、実行total += 1
しますが、total_ == 1E17
それでも、「1」は重要ではないため、1E17までの数値の64ビットdouble
表現を変更できないため、注入します。
2を実行total += 2 - 1E17
します。これ2 - 1E17
は最初に評価され、2が不正確/無意味に失われると生成されます。したがって、現在の1と2のサンプルにもかかわらず、-1E17
1E17の合計に-1E17を追加して0になります。移動平均は1.5ではなく0を計算します。別のサンプルを追加するときに、適切に組み込まれていなかったにもかかわらず、「最も古い」1を減算します。私たちと移動平均は間違ったままになる可能性があります。total_
total_
total_
total_
最新のものを格納するコードを追加できますtotal_
。現在のtotal_
値がその一部である場合(テンプレートパラメーターが乗法のしきい値を提供する可能性があります)、配列total_
内のすべてのサンプルからを再計算します(そしてnewに設定します)。十分気になる読者にお任せします。samples_
highest_recent_total_
total_