11

I was trying out this function in a C program, and it keeps printing the wrong time. This is my code at the moment:

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/resource.h>

int main( int argc, char **argv ){
    struct timespec start, finish;
    clock_gettime( CLOCK_REALTIME, &start );
    sleep( 1 );
    clock_gettime( CLOCK_REALTIME, &finish );
    printf( "%f\n", ((double) (finish.tv_nsec - start.tv_nsec))/((double) 100000) );
    return 0;
}

I'm not sure if this is an anomaly caused by rounding errors when converting to a double or if I'm using the clock_gettime() function incorrectly, but I expected it to output 1 second and instead it outputs 1.27 seconds.

Zen Hacker
  • 733
  • 2
  • 6
  • 13
  • 3
    1 nanosecond is 1000000000 seconds. Also if you start at second 33.433 and stop at second 34.42991, the difference finish.tv_nsec - start.tv_nsec is negative – pmg Dec 10 '18 at 14:52
  • 1
    That's not a reliable time delta calculation. You need to compute using the whole seconds as well as the nanoseconds in general, though you'll often get away with it. The number output is tenths of milliseconds — that's plausible for the overhead of system calls and scheduling. But: you ignore the difference of one whole second recorded in the `tv_sec` elements of the time specs. – Jonathan Leffler Dec 10 '18 at 14:53
  • So is `tv_nsec` the number of nanoseconds modulo the second? – Zen Hacker Dec 10 '18 at 14:54
  • That would explain why the number was too small when I divided by 1 billion. – Zen Hacker Dec 10 '18 at 14:54
  • 1
    The `tv_nsec` is the number of nanoseconds within the current second. It ranges (in theory) between 0 and 999,999,999. This allows an integer number of whole seconds to be stored in `tv_sec` and a fraction of a second to be stored in `tv_nsec`. Actual resolution is another issue: see `clock_getres()` for that. On a Mac, for instance, the resolution is microseconds, even though those are expressed in nanoseconds. – Jonathan Leffler Dec 10 '18 at 14:55

1 Answers1

15

You need to take into account the tv_sec member of the structure when calculating the time difference between two values returned by clock_gettime().

The tv_nsec is the number of nanoseconds within the current second. It ranges (in theory) between 0 and 999,999,999. This allows an integer number of whole seconds to be stored in tv_sec and a fraction of a second to be stored in tv_nsec. Actual resolution is another issue: see clock_getres() for that. On a Mac, for instance, the resolution is microseconds, even though those are expressed in nanoseconds.

Consider using code like this:

#include <stdio.h>
#include <time.h>
#include <unistd.h>

enum { NS_PER_SECOND = 1000000000 };

void sub_timespec(struct timespec t1, struct timespec t2, struct timespec *td)
{
    td->tv_nsec = t2.tv_nsec - t1.tv_nsec;
    td->tv_sec  = t2.tv_sec - t1.tv_sec;
    if (td->tv_sec > 0 && td->tv_nsec < 0)
    {
        td->tv_nsec += NS_PER_SECOND;
        td->tv_sec--;
    }
    else if (td->tv_sec < 0 && td->tv_nsec > 0)
    {
        td->tv_nsec -= NS_PER_SECOND;
        td->tv_sec++;
    }
}

int main(void)
{
    struct timespec start, finish, delta;
    clock_gettime(CLOCK_REALTIME, &start);
    sleep(1);
    clock_gettime(CLOCK_REALTIME, &finish);
    sub_timespec(start, finish, &delta);
    printf("%d.%.9ld\n", (int)delta.tv_sec, delta.tv_nsec);
    return 0;
}

When run (as cgt61), I get results like:

$ cgt61
1.004930000
$ cgt61
1.004625000
$ cgt61
1.003023000
$ cgt61
1.003343000
$

This was tested on a Mac; you can see that the final three digits are always zeros. In a Linux VM (Ubuntu 18.04 on a Mac), I had to add #define _POSIX_C_SOURCE 200809L to the code (because I compile with -std=c11; if I used -std=gnu11, I would have been OK), and the output was:

$ ./cgt61
1.000589528
$
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • In this `if` condition - `(td->tv_sec < 0 && td->tv_nsec > 0)`, is it possible that this `td->tv_sec < 0 ` will ever result in `true`? – H.S. Jan 05 '22 at 04:48
  • You won't get `tv_sec < 0` from a call to `clock_gettime()` (unless the system clock is set horribly wrong), but if the user has created a time before 1970-01-01 00:00:00Z, then the `tv_sec` value will be negative. – Jonathan Leffler Jan 05 '22 at 05:02
  • Fair enough. _if the user has created a time before 1970-01-01 00:00:00Z, then...._ - its only possible when someone explicitly do it on the a system otherwise there is no way that this condition `td->tv_sec < 0` will result in `true`. – H.S. Jan 05 '22 at 05:10