Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

difference of unsigned integer - standard supported way to get signed result?

assuming two arbitrary timestamps:

uint32_t timestamp1;    
uint32_t timestamp2;

Is there a standard conform way to get a signed difference of the two beside the obvious variants of converting into bigger signed type and the rather verbose if-else.

Beforehand it is not known which one is larger, but its known that the difference is not greater than max 20bit, so it will fit into 32 bit signed.

int32_t difference = (int32_t)( (int64_t)timestamp1 - (int64_t)timestamp2 );

This variant has the disadvantage that using 64bit arithmetic may not be supported by hardware and is possible of course only if a larger type exists (what if the timestamp already is 64bit).

The other version

int32_t difference;
if (timestamp1 > timestamp2) {
  difference =    (int32_t)(timestamp1 - timestamp2);
} else {
  difference = - ((int32_t)(timestamp2 - timestamp1));
}

is quite verbose and involves conditional jumps.

That is with

int32_t difference = (int32_t)(timestamp1 - timestamp2);

Is this guaranteed to work from standards perspective?

like image 840
vlad_tepesch Avatar asked Jul 02 '19 14:07

vlad_tepesch


2 Answers

You can use a union type pun based on

typedef union
{
    int32_t _signed;
    uint32_t _unsigned;
} u;

Perform the calculation in unsigned arithmetic, assign the result to the _unsigned member, then read the _signed member of the union as the result:

u result {._unsigned = timestamp1 - timestamp2};
result._signed; // yields the result

This is portable to any platform that implements the fixed width types upon which we are relying (they don't need to). 2's complement is guaranteed for the signed member and, at the "machine" level, 2's complement signed arithmetic is indistinguishable from unsigned arithmetic. There's no conversion or memcpy-type overhead here: a good compiler will compile out what's essentially standardese syntactic sugar.

(Note that this is undefined behaviour in C++.)

like image 74
Bathsheba Avatar answered Sep 23 '22 06:09

Bathsheba


Bathsheba's answer is correct but for completeness here are two more ways (which happen to work in C++ as well):

uint32_t u_diff = timestamp1 - timestamp2;
int32_t difference;
memcpy(&difference, &u_diff, sizeof difference);

and

uint32_t u_diff = timestamp1 - timestamp2;
int32_t difference = *(int32_t *)&u_diff;

The latter is not a strict aliasing violation because that rule explicitly allows punning between signed and unsigned versions of an integer type.


The suggestion:

int32_t difference = (int32_t)(timestamp1 - timestamp2);

will work on any actual machine that exists and offers the int32_t type, but technically is not guaranteed by the standard (the result is implementation-defined).

like image 23
M.M Avatar answered Sep 23 '22 06:09

M.M