diff --git a/src/utils.c b/src/utils.c index c086633b..da863813 100644 --- a/src/utils.c +++ b/src/utils.c @@ -26,6 +26,19 @@ #define MAX_PATH_LEN 1024 +/* Calculate "x * n / d" without unnecessary overflow or loss of precision. + * + * Reference: + * https://elixir.bootlin.com/linux/v6.10.7/source/include/linux/math.h#L121 + */ +static inline uint64_t mult_frac(uint64_t x, uint64_t n, uint64_t d) +{ + const uint64_t q = x / d; + const uint64_t r = x % d; + + return q * n + r * n / d; +} + static void get_time_info(int32_t *tv_sec, int32_t *tv_nsec) { #if defined(HAVE_POSIX_TIMER) @@ -40,14 +53,13 @@ static void get_time_info(int32_t *tv_sec, int32_t *tv_nsec) */ if (info.denom == 0) (void) mach_timebase_info(&info); - /* Hope that the multiplication doesn't overflow. */ - uint64_t nsecs = mach_absolute_time() * info.numer / info.denom; + uint64_t nsecs = mult_frac(mach_absolute_time(), info.numer, info.denom); *tv_sec = nsecs / 1e9; *tv_nsec = nsecs - (*tv_sec * 1e9); #else /* low resolution timer */ clock_t t = clock(); *tv_sec = t / CLOCKS_PER_SEC; - *tv_nsec = (t % CLOCKS_PER_SEC) * (1e9 / CLOCKS_PER_SEC); + *tv_nsec = mult_frac(t % CLOCKS_PER_SEC, 1e9, CLOCKS_PER_SEC); #endif }