我正在使用以下公式的平均函数
new average = old average * (n-1) / n + (new value / n)
当传递双打时,这非常有效。我的概念证明示例代码如下。
double avg = 0;
uint16_t i;
for(i=1; i<10; i++) {
int32_t new_value = i;
avg = avg*(i-1);
avg /= i;
avg += new_value/i;
printf("I %d New value %d Avg %f\n",i, new_value, avg);
}
在我的程序中,我正在跟踪收到的消息。每次我看到一条消息时,它的点击数都会增加 1,这是使用
timespec
标记的。我的目标是保持接收某种类型的消息之间的平均时间的移动平均值(如上)。
我最初的尝试是分别对
tv_nsec
和 tv_sec
进行平均,如下
static int32_t calc_avg(const int32_t current_avg, const int32_t new_value, const uint64_t n) {
int32_t new__average = current_avg;
new__average = new__average*(n-1);
new__average /= n;
new__average += new_value/n;
return new__average;
}
void average_timespec(struct timespec* average, const struct timespec new_sample, const uint64_t n) {
if(n > 0) {
average->tv_nsec = calc_avg(average->tv_nsec, new_sample.tv_nsec, n);
average->tv_sec = calc_avg(average->tv_sec, new_sample.tv_sec, n);
}
}
我的问题是我使用的是整数,这些值总是向下舍入,而我的平均值相差很大。有没有更智能/更简单的方法来平均
timespec
读数之间的时间?
下面是一些我已经使用了很多[在生产软件中]多年的代码。
主要思想是,仅仅因为
clock_gettime
使用 struct timespec
not 就意味着它必须“随身携带”到任何地方:
从
long long
获取double
或 clock_gettime
并传播 那些值会更容易。
所有进一步数学都是简单的加/减等。
clock_gettime
调用的开销使转换中的乘法/除法时间相形见绌。
我是否使用固定纳秒值或小数秒值取决于具体的应用程序。
就您而言,我可能会使用
double
,因为您已经有了适用于此的计算。
不管怎样,这就是我用的:
#include <time.h>
typedef long long tsc_t; // timestamp in nanoseconds
#define TSCSEC 1000000000LL
#define TSCSECF 1e9
tsc_t tsczero; // initial start time
double tsczero_f; // initial start time
// tscget -- get number of nanoseconds
tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
clock_gettime(CLOCK_MONOTONIC,&ts);
tsc = ts.tv_sec;
tsc *= TSCSEC;
tsc += ts.tv_nsec;
tsc -= tsczero;
return tsc;
}
// tscgetf -- get fractional number of seconds
double
tscgetf(void)
{
struct timespec ts;
double sec;
clock_gettime(CLOCK_MONOTONIC,&ts);
sec = ts.tv_nsec;
sec /= TSCSECF;
sec += ts.tv_sec;
sec -= tsczero_f;
return sec;
}
// tscsec -- convert tsc value to [fractional] seconds
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= TSCSECF;
return sec;
}
// tscinit -- initialize base time
void
tscinit(void)
{
tsczero = tscget();
tsczero_f = tscsec(tsczero);
}
如果可能,请使用有符号数学,否则下面不需要
new_value < 0
转换。
int64_t
// new__average = new__average*(n-1);
// new__average /= n;
// new__average += new_value/n;
// v-------------------------------------v Add first
new__average = (new__average*((int64_t)n-1) + new_value + n/2)/n;
// Add n/2 to effect rounding ^-^
如果必须从平均值中形成
void average_timespec(int64_t* average, struct timespec new_sample, int64_t n) {
if (n > 0) {
int64_t t = new_sample.tv_sec + new_sample.tv_nsec*(int64_t)1000000000;
*average = (*average*(n-1) + t + n/2)/n;
}
}
,当
struct timespec
时很容易做到。average >= 0
在大多数情况下,这是通过保留小数点后 N 位来完成的。不幸的是,使用
int64_t average;
average_timespec(&average, new_sample, n);
struct timespec avg_ts = (struct timespec){.tm_sec = average/1000000000,
.tm_nsec = average%1000000000);
结构,小数点将是从 0 到 999,999,999 的数字,这不是 2 的幂。我仍然认为你可以实现它。
使用支持timespec
类型的编译器,您可以先将
__int128
转换为 timespec
,然后根据该数字进行数学运算。__int128
现在您可以使用
__int128 t = ts.tv_nsec + ts.tv_sec * 1000000000;
计算平均值,其精度为小数点后 9 位。如果你想要更精确一点,比如另外 2 位数字,你可以使用:
t
即左右各乘以 100。
_注意:非常大的数字可能需要转换;因此,可能需要编写上面的内容,以确保数学按预期工作:
__int128 t = ts.tv_nsec * 100 + ts.tv_sec * 100000000000;
没有办法写出完整的
const __int128 one_hundred = 100;
const __int128 one_hundred_billion = 100000000000;
const __int128 seconds = ts.tv_sec;
const __int128 nanoseconds = ts.tv_nsec;
__int128 t = nanoseconds * one_hundred
+ seconds * one_hundred_billion;
文字数字(据我所知),您可能需要使用一些数学来使用它。
现在__int128
的精度为 11 位。用这些定点值进行数学运算 - 即加法和减法就可以了,乘法和除法需要额外的工作......
t
您仍然会遇到类似的精度误差,只是比使用普通整数小得多。只是普通整数和定点数之间的数学运算更难理解。如果使用双打就足够了,那么克雷格的解决方案是最简单的。
__int128 t3 = t1 + t2; // t3 is defined as expected
__int128 t3 = t1 - t2;
__int128 t6 = t4 * t5; // t6 is not as expected, it was multiplied by another billion
__int128 t7 = t4 * t5 / one_billion; // or `one_hundred_billion`
// t7 is valid, same number of decimal digits
__int128 t10 = t9 / t8; // t10 is missing decimal digits
__int128 t11 = t9 * one_billion / t8;
// t11 is valid
__int128 t12 = t9 / n; // this is valid if n is a regular integer (no decimal digits)
etc.
在计算
#include <time.h>
timespec_t
timespec_average(timespec_t ts_pre, timespec_t ts_post)
{
timespec_t average;
average.tv_nsec = ts_pre.tv_nsec;
// one second in nanoseconds
long sec_in_ns = 1000000000L;
// sec
average.tv_sec = ts_pre.tv_sec + ((ts_post.tv_sec - ts_pre.tv_sec) / 2);
// nsec
if (ts_pre.tv_sec == ts_post.tv_sec)
{
average.tv_nsec += (ts_post.tv_nsec - ts_pre.tv_nsec) / 2;
}
else
{
if (ts_pre.tv_nsec <= ts_post.tv_nsec)
{
average.tv_nsec += (ts_post.tv_nsec - ts_pre.tv_nsec) / 2;
if ((ts_post.tv_sec - ts_pre.tv_sec) % 2 != 0)
{
average.tv_nsec += sec_in_ns / 2;
}
}
else
{
average.tv_nsec += (sec_in_ns - ts_pre.tv_nsec + ts_post.tv_nsec) / 2;
}
while (sec_in_ns <= average.tv_nsec)
{
average.tv_nsec -= sec_in_ns;
++average.tv_sec;
}
}
return average;
}
平均值时,您必须小心不要溢出
int
边界,因为 tv_sec
没有严格定义,并且没有像 time_t
那样的上限值。基本上不要尝试将这两个值相加就可以了。