In C, How do I calculate the signed difference between two 48-bit unsigned integers?

Refresh

November 2018

Views

235 time

8

I've got two values from an unsigned 48bit nanosecond counter, which may wrap.

I need the difference, in nanoseconds, of the two times.

I think I can assume that the readings were taken at roughly the same time, so of the two possible answers I think I'm safe taking the smallest.

They're both stored as uint64_t. Because I don't think I can have 48 bit types.

I'd like to calculate the difference between them, as a signed integer (presumably int64_t), accounting for the wrapping.

so e.g. if I start out with

x=5

y=3

then the result of x-y is 2, and will stay so if I increment both x and y, even as they wrap over the top of the max value 0xffffffffffff

Similarly if x=3, y=5, then x-y is -2, and will stay so whenever x and y are incremented simultaneously.

If I could declare x,y as uint48_t, and the difference as int48_t, then I think

int48_t diff = x - y; 

would just work.

How do I simulate this behaviour with the 64-bit arithmetic I've got available?

(I think any computer this is likely to run on will use 2's complement arithmetic)

P.S. I can probably hack this out, but I wonder if there's a nice neat standard way to do this sort of thing, which the next person to read my code will be able to understand.

P.P.S Also, this code is going to end up in the tightest of tight loops, so something that will compile efficiently would be nice, so that if there has to be a choice, speed trumps readability.

3 answers

5

Вы можете имитировать 48-битовое беззнаковое целое, просто тип маскирования верхние 16 битов uint64_tпосле того, как какой - либо арифметической операции. Так, к примеру, взять разницу между этими двумя временами, вы могли бы сделать:

uint64_t diff = (after - before) & 0xffffffffffff;

Вы получите правильное значение, даже если счетчик обернутого вокруг во время процедуры. Если счетчик не обернуть вокруг, маскировка не нужна, но не вреден либо.

Теперь , если вы хотите , эта разница будет признана как знаковое целое вашего компилятора, вы должны подписать продлить 48 - й биты. Это означает , что если 48 - й бит установлен, то число отрицательное, и вы хотите установить 49 - й по 64 - й бит в 64-разрядное целое число. Я думаю , что простой способ сделать это:

int64_t diff_signed = (int64_t)(diff << 16) >> 16;

Внимание: Вы , вероятно , следует проверить это , чтобы убедиться , что он работает, а также остерегайтесь есть реализация определенных поведение , когда я бросил uint64_tАнь int64_t, и я думаю , что есть реализация определенных поведение , когда я смещаться подписанную отрицательное число справа. Я уверен , что язык C адвокат мог бы некоторые с чем - то более надежным.

Обновление: ОП указывает на то , что если совместить операцию взятия разности и делает знаковое расширение, нет необходимости в маскировке. Это будет выглядеть следующим образом :

int64_t diff = (int64_t)(x - y) << 16 >> 16;
3
struct Nanosecond48{
    unsigned long long u48 : 48;
//  int res : 12; // just for clarity, don't need this one really
};

Здесь мы просто используем явную ширину поля, чтобы быть 48 бит и с этим (правда, несколько неловко) типа вы жить до вашего компилятора, чтобы правильно обращаться с различными архитектурами / платформы / этажерку.

Как следующее:

Nanosecond48 u1, u2, overflow;
overflow.u48 = -1L;
u1.u48 = 3;
u2.u48 = 5;
const auto diff = (u2.u48 + (overflow.u48 + 1) - u1.u48) & 0x0000FFFFFFFFFFFF;

Конечно , в последнем заявлении вы можете просто сделать операцию остатка с , % (overflow.u48 + 1)если вы предпочитаете.

2

Вы знаете, что было раньше, чтение и который был позже? Если так:

diff = (earlier <= later) ? later - earlier : WRAPVAL - earlier + later;

где WRAPVALэто (1 << 48)довольно легко читать.