问题
So I wrote a little experiment testing underflowing & overflowing, using c and a 64 bit machine. For type int the min/max values are:
int tmax = 2147483647;
int tmin = -2147483648;
I know understand how two's-complement works, that isn't my question.
So I thought, well what happens if I make something negative tmin? That is:
int tmin = -2147483648;
int negativeTmin = -tmin;
It ends up still being tmin. (That is, negativeTmin will be -2147483648)
My question is why is that? Since positive 2,147,483,648 cannot be represented by int I understand why it isn't that of course, but it seems odd that it doesn't change at all for that makes it the only non-zero int that doesn't change when - is applied to it. I'm not saying I have a better idea of what it should be, I am just curious as to why -tmin == tmin. Does it have something to do with bitwise operations, or how subtraction is done in a computer, or does it default to doing this because what I'm trying to do is undefined, or something else?
My code:
#include <stdio.h>
int main() {
int tmax = 2147483647;
printf("tmax Before: %d\n", tmax);
tmax++;
printf("tmax After: %d\n\n", tmax);
int tmin = -2147483648;
printf("tmin Before: %d\n", tmin);
tmin--;
printf("tmin After: %d\n\n", tmin);
int tmin2 = -2147483648;
int negativeTmin = -tmin2;
printf("negative tmin: %d\n\n", negativeTmin);
return 0;
}
Output:
tmax Before: 2147483647 tmax After: -2147483648
tmin Before: -2147483648 tmin After: 2147483647
negative tmin: -2147483648
回答1:
As other folks have posted here, technically speaking what you're doing leads to undefined behavior because overflow or underflow of a signed integer in C leads to undefined behavior.
On the other hand, on most Intel systems, an integer overflow or underflow just wraps around the integer value around and sets some processor flag so that future instructions can detect the overflow. On those systems, it's reasonable to ask - why do you get Tmin when you compute -Tmin?
In a signed two's complement system, it's good to note that the expression -x
is equivalent to ~x + 1
. So let's imagine you have Tmin, which looks like this:
10000000 00000000 00000000 00000000
If you compute ~Tmin, you get
01111111 11111111 11111111 11111111
This happens to be Tmax. If you add one to this, you get a massive ripple carry propagating all the way to the end, yielding
10000000 00000000 00000000 00000000
which is what we started with. So that's why you're probably seeing Tmin come back.
Another way to see this: you know that Tmin for a signed 32-bit integer is -231. The value of -Tmin should be a value such that Tmin + -Tmin = 0 (mod 232). So which value in the range [-231, 231 - 1] happens to have this property? It's -231, which is why Tmin = -Tmin.
So the best answer to your question is probably "technically what you're doing is undefined behavior, but on a reasonable Intel system and a compiler that isn't set to do aggressive optimizations, it comes down to the mechanics of how signed 32-bit integer arithmetic works and how negation is defined."
回答2:
Your code int tmin2 = -2147483648; int negativeTmin = -tmin2
introduces undefined behaviour due to integer overflow, so it may yield any result. So thinking about any rules why this happens and if it has to do with two's complement makes no sense and is actually wrong.
An integral overflow is the example for undefined behaviour, as it is mentioned as example in the standard's definition of "undefined behaviour" (3.4.3 - undefined behavior):
1 undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements
2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
3 EXAMPLE An example of undefined behavior is the behavior on integer overflow.
回答3:
-X is 2s complement of X
And that is what the hardware does for negation https://c9x.me/x86/html/file_module_x86_id_216.html
INT_MIN = -2147483648 = 0x80000000
2's complement of -2147483648 = 0x80000000
You can calculate 2's complement as flip bits and add 1
see https://en.wikipedia.org/wiki/Two%27s_complement
Flipping bits of 0x80000000 gives 0x7fffffff.
0x7fffffff + 1 = 0x80000000 = -2147483648
(gdb) p /x (int)(-2147483648)
$14 = 0x80000000
(gdb) p /x (int)-(-2147483648)
$15 = 0x80000000
(gdb) p /x ~(0x80000000)
$16 = 0x7fffffff
(gdb) p /x ~(0x80000000) + 1
$17 = 0x80000000
回答4:
One further way to think about it.
Data with type int
is represented with 32 bits. Take tmin = -2147483648, then, of course, -tmin = 2147483648. In two's complement arithmetic, the binary representation for tmin is
10000000 00000000 00000000 00000000
and, for -tmin,
0 10000000 00000000 00000000 00000000
But only 32 bits are allowed, so the truncation eliminates the most significant bit (in this case, the first zero) and we get
10000000 00000000 00000000 00000000
which is tmin.
回答5:
Actually I believe I just realized the answer; as we know 2147483648 cannot be represented by int and so is -2147483648, so when you make -(-2147483648) well that is 2147483648, but that cannot be represented so as described above that becomes -2147483648. Hence why -tmin == tmin.
来源:https://stackoverflow.com/questions/45089640/why-does-the-negation-of-the-minimum-possible-integer-yield-itself