C语言 计算时间间隔的平均时间

elcex8rz  于 2023-05-06  发布在  其他
关注(0)|答案(3)|浏览(172)

我正在使用一个平均函数,它遵循以下公式

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_nsectv_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读数之间的时间?

pes8fvy9

pes8fvy91#

下面是我多年来一直在生产S/W中使用的代码。
主要的想法是,仅仅因为clock_gettime使用struct timespec并不意味着它必须到处“携带”:
1.更容易转换为long longdouble,并在从clock_gettime获得 * 这些 * 值后立即传播它们。
1.所有 * 进一步 * 数学是简单的加/减,等等。

  1. 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);
}
tkqqtvp1

tkqqtvp12#

1.使用更好的整数数学。

  • 如果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  ^-^

1.回顾一下,在两个部分中做平均的整个想法是有缺陷的。而是使用64位纳秒计数。直到2263年。
建议代码:

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

int64_t average;
average_timespec(&average, new_sample, n);
struct timespec avg_ts = (struct timespec){.tm_sec = average/1000000000, 
    .tm_nsec = average%1000000000);
kkbh8khc

kkbh8khc3#

如果你想保留整数,你需要用定点数字做数学运算。这有点棘手
在大多数情况下,这是通过在小数点后保留N位来完成的。不幸的是,对于timespec结构,小数点将是从0到999,999,999的数字,这不是2的幂。我仍然认为你能做到。
使用支持__int128类型的编译器,可以先将timespec转换为__int128,然后根据该数字进行数学运算。

__int128 t = ts.tv_nsec + ts.tv_sec * 1000000000;

现在,您可以使用t计算平均值,其精度为小数点后9位。如果你想要更精确一点,比如说另一个2位数,你可以用途:

__int128 t = ts.tv_nsec * 100 + ts.tv_sec * 100000000000;

即将左和右再乘以100。
_注意:非常大的数字可能需要转换;因此,上面的代码可能需要编写为确保数学按预期工作:

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文字数(我知道),你可能需要使用一些数学来使用这样的。
现在t作为11位数的精度。用这些固定点值做数学运算--即加法和减法都很好,乘法和除法需要额外的工作...

__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.

您仍然会有类似的精度错误,只是比使用普通整数少得多。只是普通整数和定点数之间的数学比较难理解。如果使用double就足够了,那么克雷格的解决方案是最简单的。

相关问题