2

Here's the code:

int a;
a = 2147483648 + 2147483648;
printf("%d", a);

I know that maximum number of int variable is 2147483647. So, as I know, 2147483648 = -2147483648. But why 2147483648 + 2147483648 = 0?

M.M
  • 130,300
  • 18
  • 171
  • 314
Hoseong Jeon
  • 1,150
  • 8
  • 20
  • 2
    Well, you see, 2147483648 is 0x80000000 + 0x80000000 = 0x100000000. That doesn't fit in 32 bits, so the last 1 is lost, and it becomes 0. – mukunda May 06 '20 at 00:28
  • Look at how it's represented in binary and how the computer adds the numbers. – Brady Dean May 06 '20 at 00:28
  • 1
    If you know that `a = -a` then add `a` to both sides... `a+a = -a+a` i.e. `0` – M.M May 06 '20 at 00:29
  • 1
    @BradyDean C arithmetic is defined in terms of values, not representations – M.M May 06 '20 at 00:53

3 Answers3

5

2147483648 is 1 followed by 31 zeroes. If you add it twice, it simply overflows (all 32 bits will be zero and carry will be set). Since carry is basically discarded (ignored, when you store the value into a), you don't see it, all you see are 0s.

   10000000 00000000 00000000 00000000  
  +10000000 00000000 00000000 00000000  
  ------------------------------------  
(1)00000000 00000000 00000000 00000000 
  • 2
    "carry is basically discarded" is not true . `2147483648` is likely to be a 64-bit value on modern system and "carry" from bit 31 goes to bit 32 – M.M May 06 '20 at 00:34
  • 1
    Well I spoke about 32bit integers. You're right that carry goes to 32nd bit on x64, but when you attempt to store the number from register into a memory, it really is discarded IMO. – Tomáš Chabada May 06 '20 at 00:36
  • 1
    x86 also has 64-bit values . You're right in the comment that storing the result of the addition into `a` might lose bits, but your answer doesn't reflect this. The result of the addition does not cause any overflow and still contains the `1` (on a C99 compliant system). You could try `printf("%lld", 2147483648 + 2147483648);` – M.M May 06 '20 at 00:39
4

Because your constants do not fit in an int they are treated as a long (or, if necessary, long long, so:

2147483648 = 0x80000000 + another 0x80000000 = 0x100000000, which when you assign it to a is truncated to 0 (assuming 4 byte ints.)

gcc issues a warning for the assignment.

Second one of these today.

Paul Sanders
  • 15,937
  • 4
  • 18
  • 36
  • But, if I change int to long long, and %d to %lld, it gives me same result. Why? – Hoseong Jeon May 06 '20 at 00:30
  • 2
    "is truncated to 0" - it's implementation-defined what happens here (other possibilities include raising a signal) – M.M May 06 '20 at 00:31
  • @Hoseong The format specifier has to match the type of `a`, which is an `int`. The truncation happens in the assignment, not the `printf`. – Paul Sanders May 06 '20 at 00:31
  • @HoseongJeon this works: https://wandbox.org/permlink/3YLOUDHtMGxbQsBP – Ryan Haining May 06 '20 at 00:32
  • Well, that code you linked doesn't work in Visual Studio 2019. I think that you're right, but why doesn't work in VS2019? – Hoseong Jeon May 06 '20 at 00:36
  • @HoseongJeon because in MSVC `long` is a 32-bit type, but on 64-bit Linux it's a 64-bit type – phuclv May 06 '20 at 00:36
  • @HoseongJeon ... So use long long (and %lld) instead. – Paul Sanders May 06 '20 at 00:37
  • @phuclv Ahh... So is long long 32-bit int MSVC too? 'cause when I change long to long long, it works same. – Hoseong Jeon May 06 '20 at 00:37
  • @PaulSanders When I change long to long long, and %ld to %lld, it works equally. – Hoseong Jeon May 06 '20 at 00:38
  • @HoseongJeon You're right. Maybe MSVC doesn't promote the constants to a wider type. Try 2147483648LL in the summation. – Paul Sanders May 06 '20 at 00:40
  • @PaulSanders Yeah. When I try with 2147483648LL, that works. But [here](https://docs.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=vs-2019) says that long long is 8 byte!! What?! – Hoseong Jeon May 06 '20 at 00:44
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213206/discussion-between-hoseong-jeon-and-paul-sanders). – Hoseong Jeon May 06 '20 at 00:45
  • 1
    MSVC applies the C90 rules for constants in the range 2^31 to 2^32-1, even while also supporting long long (this does not conform to any standard). Or it used to anyway, I'm not up to date with any potential recent developments in MSVC – M.M May 06 '20 at 00:58
  • @M.M MSVC is the first compiler to fully support C++17 so it must conform to C++11 and up where `long long` exists, unless you're chosing to use an older standard. \@HoseongJeon there are so many wrong things in your comments. The standard says that **`long long` must have at least 64 bits** and the same applies to MSVC. Promotion only applies to types smaller than int so obviously there's no promotion occur if you do `2147483648LL + 2147483648LL` – phuclv May 06 '20 at 01:42
  • @RyanHaining it only works if `long` is wider than 32 bits (like on 64-bit Linux) – phuclv May 06 '20 at 01:53
  • @phuclv ah of course. I should have used int64_t: https://wandbox.org/permlink/Xw0AAFISPDlr4z0X – Ryan Haining May 06 '20 at 02:02
  • @phuclv this is a C question – M.M May 06 '20 at 02:52
3

The behaviour of this code depends on:

  • which version of C you are using
  • what the sizes of types are on your compiler (implementation-defined)
  • perhaps other implementation-defined features

The code could output any number or raise a signal but this must be covered in the compiler's documentation.

There are two places in which the code relies on implementation-defined behaviour: the result of the addition, and then the operation of storing the result of the addition into an int variable.

Also I would like to point out that C arithmetic is based on values, not representations. The answer does NOT depend on 2's complement or binary carries or anything like that. 2147483648 is always a large positive integer , it is not a negative number. Adding two positive numbers cannot produce a negative number either. This is commonly misunderstood.


Here are some example cases:

  • In a C90 implementation where long is 32 bits, 2147483648 has type either unsigned int or unsigned long. According to the definition of unsigned arithmetic the result is the mathematical value of 2147483648 + 2147483648 modulo 2^32 which works out to 0.
  • On an implementation where int is 32-bit and long is 64-bit, 2147483648 has type long. Then the result of the addition has type long and value 4294967296 . Then, assigning this value to an int is an out-of-range assignment causing implementation-defined behaviour. One common way that implementations define this is truncating higher bits. Raising a signal is another option.
  • On a normal C99 implementation with 32-bit long and 64-bit long long then the case is quite similar to the previous bullet except the type is long long.
  • There could be some esoteric system with a 33-bit long where the addition then causes undefined behaviour due to overflow , but we generally don't worry about that and assume nobody would ever design such a system . (There are systems with 36-bit integers though!)
  • Non-conforming compilers such as MSVC could of course do something different .
M.M
  • 130,300
  • 18
  • 171
  • 314
  • Okay. I think you're talking some great ideas. Can you answer to [here](https://stackoverflow.com/questions/61625574/msvc-2019-long-long-variable), which I just asked about MSVC long long? – Hoseong Jeon May 06 '20 at 01:01
  • Re “assume nobody would ever design such a system”: I could see somebody implementing C with tweakable settings for purposes of exploring the standard, testing code for incorrect assumptions about portability, and so on, rather for the use of directly producing production executables. – Eric Postpischil May 06 '20 at 01:03
  • @phuclv https://gcc.godbolt.org/z/uPdHYs , oops . This cannot give `4` in ISO C99 or later, nor in ISO C++11 or later. (Assuming 8-bit char). Also, this is a C question and you link to a C++ conformance page. – M.M May 06 '20 at 03:02