私は質問は自己説明だと思う、私はそれがおそらくオーバーフローに関係していると思うが、それでも私はそれをよく理解していない。ビット単位で、何が起こっているのでしょうか?
なぜ -(-2147483648) = -2147483648
になるのでしょうか(少なくともCでコンパイルしている間は)?
式 -(-2147483648)
はCで完全に定義されているが,なぜこのように定義されているかは自明ではないかもしれない.
2147483648と書くと、単項のマイナス演算子を整数の定数に適用した形になります。もし
2147483648が
intとして表現できない場合は、
longまたは
long long`* (どちらか先に適合する方) として表現します。後者は C 標準によって、この値 † が保証された型となります。
それを確認するために、次のように調べるとよいでしょう。
printf("%zu\n", sizeof(-2147483648));
で調べると、私のマシンでは 8
が得られます。
次に2番目の -
演算子を適用すると、最終的には 2147483648L
となります (最終的に long
と表現されたと仮定しています)。これを int
オブジェクトに代入しようとすると、次のようになります。
int n = -(-2147483648);
というように int
オブジェクトに代入しようとすると、実際の動作は implementation-defined となります。標準規格を参照する。
C11 §6.3.1.3/3 符号付き整数と符号なし整数 ###.
そうでない場合、新しい型は符号付きで、値を表現することはできません。 結果は実装で定義されるか、実装で定義されたシグナルが発生します。 実装で定義されたシグナルが発生します。
最も一般的な方法は、単純に上位ビットをカットオフすることです。例えば、GCC ドキュメントでは、次のようになっています。
幅Nの型に変換する場合、値は2^Nのモジュロで減らされる。 型の範囲内になるように>シグナルは発生しない。
概念的には、幅32の型への変換は、ビット単位のAND演算で説明できる。
value & (2^32 - 1) // preserve 32 least significant bits
2'補数]2演算により、n
の値はすべて0とMSB(符号)ビットが設定されたものとなり、 -2^31
すなわち -2147483648
という値を表わします。
int
オブジェクトを負にする。もし、-2147483648
の値を持つ int
オブジェクトを否定しようとすると、2'補数マシンを想定した場合、プログラムは 未定義の動作 を示すでしょう。
n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647
C11 §6.5/5 数式##.
式の評価中に例外的な条件が発生した場合(結果が数学的に定義されていない場合、または結果が数学的に定義されている場合)。 式の評価中に例外条件*が発生した場合(つまり、結果が数学的に定義されていない場合や その型に対して表現可能な値の範囲にない場合)、その動作は未定義です。 は未定義です。
*)撤回された C90 Standard では、long long
型は存在せず、ルールも異なっていました。具体的には、接尾辞なしの10進数に対するシーケンスは int
, long int
, unsigned long int
(C90 §6.1.3.2 Integer constants).</sup>.C90 §6.1.3.1 Integer constants).
†) これは LONG_MAX
のせいで、最低でも +9223372036854775807
でなければならない (C11 §5.2.4.2.1/1).
注:この回答は、多くのコンパイラで使用されている旧ISO C90規格には当てはまりません。
まず、C99, C11では、-(-2147483648) == -2147483648
という式は実際にはfalseとなります。
int is_it_true = (-(-2147483648) == -2147483648);
printf("%d\n", is_it_true);
を印刷します。
0
では、どうしてこれが真と評価されるのでしょうか?
このマシンは32ビットの2's complement 整数を使っています。2147483648は 32 ビットに収まらない整数定数なので、最初に収まる場所によって
long intか
long long intのどちらかになります。これを否定すると、
-2147483648となります。繰り返しますが、
-2147483648という数字は32ビットの整数に収まるにもかかわらず、
-2147483648という表現は >32 ビット正の整数の前に
-という単項を置いたものです!この式は
-2147483648` という数字が 32 ビットの整数に収まることを意味します。
次のプログラムを試してみてください。
#include <stdio.h>
int main() {
printf("%zu\n", sizeof(2147483647));
printf("%zu\n", sizeof(2147483648));
printf("%zu\n", sizeof(-2147483648));
}
このようなマシンでは、おそらく4, 8, 8が出力されるでしょう。
ここで、-2147483648
を否定すると、再び +214783648
となり、これは long int
または long long int
型であり、すべてうまくいきます。
*C99, C11 では、整数の定数式 -(-2147483648)
は、すべての準拠した実装で適切に定義されています。
さて、この値を int
型の変数に代入すると、32ビットで2'補数表現となり、その値は表現できません。32ビットで2'補数の値は -2147483648 から 2147483647 までの範囲となります。
C11規格 6.3.1.3p3 では、整数の変換について次のように言っています。
- [時]新しい型が符号付きで、値がそれで表現できない場合、結果は実装で定義されたか、 実装で定義された信号が発生します。
つまり、C言語規格では、この場合の値が実際にどうなるかは定義していませんし、 シグナルが発生してプログラムの実行が停止する可能性も排除しておらず、 それをどう扱うかは実装(=コンパイラ)に委ねています(C11 3.4.1):
実装で定義された動作 です。
各実装がどのように選択するかを文書化した不特定な動作
and (3.19.1):
実装で定義された値 です。
各実装がどのように選択するかを文書化した不特定値
あなたの場合、実装で定義された動作は、最下位32ビット[*]の値です。2's 補数のため、(long) long int 値 0x80000000
はビット 31 がセットされ、他のすべてのビットがクリアされます。32 ビット 2's 補数の場合、ビット 31 は符号ビットで、数値が負の値であることを意味します。
[*] GCCはこの場合、実装で定義された動作を次のように記録しています](https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html)。
整数を符号付き整数型に変換した結果、またはその型のオブジェクトで値を表現できないときに発生するシグナル (C90 6.2.1.2, C99 and C11 6.3.1.3) 。
幅 N
の型への変換では,その型の範囲に収まるように 2^N
のモジュロ倍率で値が減らされ,シグナルは送出されない.
これはC言語の質問ではありません。int型の32ビット2'補数表現を特徴とするC言語の実装では、値
-2147483648を持つ
int` に単項否定演算子を適用した結果は undefined となります。 つまり、C言語では、このような演算の評価結果を指定することは特に禁止されています。
しかし、もっと一般的に、単項演算子 -
が 2 の補数演算でどのように定義されているかを考えてみましょう:正の数 x の逆数は、その 2 進表現のすべてのビットを反転して 1
を加えることで形成されます。 この定義は、符号ビット以外の少なくとも1つのビットが設定されている負の数に対しても同様に適用されます。
しかし、値のビットが設定されていない2つの数については、マイナーな問題が発生します。0は全くビットが設定されておらず、符号ビットだけが設定されている数(32ビット表示で-2147483648)である。 このどちらかのビットをすべて反転させると、すべての値ビットが設定された状態になります。 したがって、その後に1を足すと、その結果が値ビットをオーバーフローしてしまいます。 符号ビットを値ビットとして扱い、符号なしと同じように足し算をすることをイメージすると
-2147483648 (decimal representation)
--> 0x80000000 (convert to hex)
--> 0x7fffffff (flip bits)
--> 0x80000000 (add one)
--> -2147483648 (convert to decimal)
ゼロを反転させる場合も同様ですが、その場合は1を加算したときのオーバーフローで、それまでの符号ビットもオーバーフローします。 このオーバーフローを無視すると、32個の下位ビットはすべて0になるので、-0 == 0となります。