8

How can I best convert a uint32_t to an int32_t quickly with wrapping, in C++?

Some tries:

uint32_t y = UINT32_MAX;
int32_t x = (int32_t)y; // UB on overflow
int32_t x = *(int32_t*)&y; // does this violate strict aliasing?
Demi
  • 3,535
  • 5
  • 29
  • 45
  • 2
    Isn't it theoretically impossible to convert it without risk of overflow? – pingul Apr 18 '16 at 21:20
  • What should it do when overflow occurs? Wrap two's complement (which is what "undefined behavior" actually becomes on 99% of all compilers/CPUs in the world)? – ShadowRanger Apr 18 '16 at 21:20
  • 1
    I assume `y` is declared as `uint32_t y;` (it would be good to add this to the question) – M.M Apr 18 '16 at 22:15

3 Answers3

9

int32_t x = (int32_t)y; is not overflow and not UB. Overflow is when an arithmetic operation produces a result outside the range of representable values. However, a conversion is not an arithmetic operation.

This situation is implementation-defined behaviour. All implementations that I'm aware of define the behaviour as making no change in the representation.

Note that no cast is necessary here. You can write int32_t x = y;. In practical terms, this is simpler and will always work. So much code relies on this that no vendor is ever going to define any other behaviour (not that they have any reason to do so anyway).


int32_t x = *(int32_t*)&y is not UB. It does not violate strict aliasing because the signed version of a type is allowed to alias the unsigned version. This code is guaranteed to produce the int32_t with the same representation as the corresponding uint32_t (i.e. "wrapping", since these types are guaranteed to be 2's complement).

M.M
  • 138,810
  • 21
  • 208
  • 365
5
union { 
  int32_t i; 
  uint32_t u;
} u;
u.i = ...;
printf("%" PRIu32 "\n", u.u);

This and memcpy(&uint_var, &int_var, sizeof uint_var) are the two standard ways to do such a conversion without invoking undefined behavior.

See also:

Community
  • 1
  • 1
a3f
  • 8,517
  • 1
  • 41
  • 46
  • What about trap representations for signed int? – Roland Illig Apr 18 '16 at 21:28
  • @RolandIllig Exact width integers have no padding bits and as such can't have trap representations. – a3f Apr 18 '16 at 21:32
  • Reading 6.2.6.2p2 of C99 seems to say otherwise. While there may not be a padding bit, there can still be a trap representation. – Roland Illig Apr 18 '16 at 21:39
  • 2
    I shall stand corrected. According to C99, 7.18.1.1, `int32_t` is guaranteed to be two’s complement, and together with 7.18.2.1, there can be no trap representation. Thanks for enlightening me. – Roland Illig Apr 18 '16 at 21:45
  • 2
    In C++ it is undefined behaviour to read a member of a union not the same as the last one written. (This question was originally dual-tagged but is actually a C++ question) – M.M Apr 18 '16 at 22:23
  • @RolandIllig: Some two's-complement implementations have a range -MAX_INT to +MAX_INT; the section numbers for C99 don't match N1570, so I can't see what text you're quoting, but would it forbid implementations from defining INT_MIN as -2147483647 [e.g. allowing x/-2 to be evaluated as -x/2]? – supercat Apr 19 '16 at 17:09
  • @supercat For `int`, your arguments hold. But this question is about `int32_t`, and C99 says in section “7.18.1.1 Exact -width integer types” that this type is always two’s complement with no padding bits, and in section “7.18.2.1 Limits of exact-width integer types” that `INTx_MIN` is exactly `−2^(N-1)`, which leaves no room for any trap representation. – Roland Illig Apr 19 '16 at 19:44
  • @RolandIllig: I knew padding bits were forbidden, but I didn't know INT_MIN was specified as well. – supercat Apr 20 '16 at 18:57
  • @RolandIllig: BTW, would anything in the Standard forbid an implementation whose documentation specified that conversion of any (uint32_t)x greater than 2147483647 to (int32_t) would yield 42 if the program was compiled on a Tuesday, and x-4294967296 otherwise, if the implementation behaved as documented? I know no implementation would be apt to do such a thing unless the designer was being deliberately obtuse, but would anything forbid it? – supercat Apr 21 '16 at 20:02
  • @supercat: Conversion from an unsigned integer type to a signed integer type is defined in C99 6.3.1.3p3, which says that the result is implementation-defined. Paragraph 6.2.6.2p2 defines the sign bit of `int32_t` to have the value `2^−31`, which means that `2^31` is indeed unrepresentable in an `int32_t`, and therefore your tuesday-42 compiler—assuming proper documentation—would be standards-compliant. (No guarantee though, I don’t have all the paragraphs right in my head.) – Roland Illig Apr 21 '16 at 21:16
  • @RolandIllig: Conversion by type-punning would be fully defined by the Standard, and there should be no sensible reason for a typecast to do anything else, but there's also no sensible reason for `uint32_t(uint16_t x, uint16_t y) {return x*y;}` to do anything other than yield an arithmetically-correct result for all values of x and y on a system where "int" is 32 bits [which is what the authors of the C89 Standard have said they'd expect two's-complement platforms with silent overflow to do], but nothing in the Standard forbids obtuseness. – supercat Apr 21 '16 at 22:53
1
#include <assert.h>
#include <limits.h>
#include <stdlib.h>

int makesigned(unsigned x) {
  if (x <= (unsigned) INT_MAX) {
    return (int) x;
  }

  /* assume 2's complement */
  if (x >= (unsigned) INT_MIN) {
    return 0 - (int)(-x);
  }

  abort();
  return 0;
}

int main(void) {
  assert(makesigned(0) == 0);
  assert(makesigned(INT_MAX) == INT_MAX);
  assert(makesigned(UINT_MAX) == -1);
  assert(makesigned(INT_MIN) == INT_MIN);
  return 0;
}
Roland Illig
  • 40,703
  • 10
  • 88
  • 121