web-dev-qa-db-ja.com

32ビットマシンで-(-2147483648)=-2147483648なのはなぜですか?

質問は自明であると思います、おそらくオーバーフローと何か関係があると思いますが、それでも私はそれをまったく理解していません。フードの下で、ビットごとに何が起こっていますか?

なぜ-(-2147483648) = -2147483648(少なくともCでコンパイル中)なのですか?

61
Lesscomfortable

(接尾辞なしの)整数定数の否定:

-(-2147483648)はCで完全に定義されていますが、なぜこのようになっているのかは明らかではないかもしれません。

-2147483648と記述すると、整数定数に適用される単項マイナス演算子として形成されます。 2147483648intとして表現できない場合、それはlongまたはlong longとして表されます。* (最初に適合する方)、後者のタイプはその値をカバーするためにC標準によって保証されています

それを確認するには、次の方法で調べます。

printf("%zu\n", sizeof(-2147483648));

私のマシンでは8になります。

次のステップは、2番目の-演算子を適用することです。この場合、最終値は2147483648Lです(最終的にlongとして表されると仮定)。次のようにintオブジェクトに割り当てようとした場合:

int n = -(-2147483648);

実際の動作はimplementation-definedです。標準の参照:

C11§6.3.1.3/ 3符号付きおよび符号なし整数

それ以外の場合、新しい型は署名され、値はその型で表現できません。結果は実装定義であるか、実装定義信号が発生します。

最も一般的な方法は、上位ビットを単にカットオフすることです。たとえば、GCC documents it:

幅Nの型への変換の場合、値は2 ^ Nを法として減少し、型の範囲内になります。シグナルは発生しません。

概念的に、幅32のタイプへの変換は、ビット単位のAND演算で説明できます。

value & (2^32 - 1) // preserve 32 least significant bits

2の補数 算術に従って、nの値はすべてゼロとMSB(符号)ビットセットで形成され、-2^31の値、つまり-2147483648

intオブジェクトの否定:

-2147483648の値を保持するintオブジェクトを無効にしようとすると、2の補数マシンを想定して、プログラムはundefined behavior

n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647

C11§6.5/ 5式

式の評価中に例外条件が発生した場合(つまり、結果が数学的に定義されていない場合、またはその表現可能な値の範囲内にない場合タイプ)、動作は未定義です。

追加の参照:


*)廃止されたC90標準では、long longタイプはなく、ルールは異なっていました。具体的には、接尾辞なしの10進数のシーケンスはintlong intunsigned long int(C90§6.1.3.2整数定数)でした。

†)これはLLONG_MAXによるもので、少なくとも+9223372036854775807でなければなりません(C11§5.2.4.2.1/ 1)。

74

注:この回答は、多くのコンパイラでまだ使用されている古いISO C90標準には適用されません

まず、C99、C11では、式-(-2147483648) == -2147483648は実際にはfalseです:

int is_it_true = (-(-2147483648) == -2147483648);
printf("%d\n", is_it_true);

プリント

0

では、これがどのように真と評価される可能性がありますか?マシンは32ビット 2の補数 整数を使用しています。 2147483648は、32ビットにはまったく収まらない整数定数です。したがって、最初に収まる方に応じて、long intまたはlong long intのいずれかになります。これを否定すると-2147483648になります。また、数値-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.3p は、次の整数変換を示しています。

  • [いつ]新しいタイプが署名され、そのタイプで値を表すことができません。結果はimplementation-definedまたはimplementation-definedシグナル発生しました。

つまり、C標準では、この場合の値が実際に何であるかを定義していないか、シグナルの発生によりプログラムの実行が停止する可能性を排除せず、実装(つまり、コンパイラ)処理方法を決定する (C11 3.4.1)

実装定義の動作

各実装が選択方法を文書化する不特定の動作

および (3.19.1)

実装定義値

各実装が選択方法を文書化する不特定の値


あなたの場合、実装定義の動作は、値が32個の最下位ビット[*]であることです。 2の補数のため、(long)long int値0x80000000にはビット31が設定され、他のすべてのビットはクリアされます。 32ビットの2の補数の整数では、ビット31は符号ビットです-数値が負であることを意味します。すべての値ビットがゼロになっているということは、その値が表現可能な最小数、つまりINT_MINであることを意味します。


[*] GCC この場合の実装定義の動作を次のように文書化します

その型のオブジェクトで値を表現できない場合に、整数を符号付き整数型に変換した結果、またはその信号(C90 6.2.1.2、C99およびC11 6.3.1.3) 。

Nの型への変換の場合、値は2^Nを法として減じられ、型の範囲内になります。シグナルは発生しません。

16
Antti Haapala

これはCの質問ではありません。タイプintの32ビットの2の補数表現を特徴とするC実装では、値-2147483648を持つintに単項否定演算子を適用した結果です。 未定義です。つまり、C言語は、このような操作を評価した結果の指定を明確に否定します。

しかし、より一般的に、単項-演算子が2の補数演算でどのように定義されるかを考えてみましょう。正数の逆数xはバイナリ表現のすべてのビットを反転し、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です。

6
John Bollinger

数学を単純にするために4ビットの数値を使用しますが、考え方は同じです。

4ビットの数値では、有効な値は0000〜1111です。0〜15になりますが、負の数値を表す場合は、最初のビットを使用して符号を示します(正の場合は0、負の場合は1)。

したがって、1111は15ではありません。最初のビットは1なので、負の数です。その値を知るために、前の回答ですでに説明したように、「ビットを反転して1を追加する」という2補数法を使用します。

  • ビットの反転:0000
  • 追加1:0001

2進数の0001は10進数の1なので、1111は-1です。

2の補数の方法は両方の方法で行われるため、任意の数値で使用すると、その数値の逆符号付きのバイナリ表現が得られます。

さて、1000を見てみましょう。最初のビットは1なので、負の数です。 2補数法を使用する:

  • ビットを反転:0111
  • 1を追加:1000(10進数で8)

1000は-8です。 -(-8)を実行すると、バイナリで-(1000)を意味します。これは、実際には1000で2補数メソッドを使用することを意味します。上で見たように、結果も1000です。 、-(-8)は-8です。

32ビットの数値では、バイナリの-21474836481000..(31 zeroes)ですが、2の補数法を使用すると、同じ値になります(結果は同じ数値になります)。

それが、32ビット数で-(-2147483648)-2147483648と等しい理由です

1
user7605325

それは、Cのバージョン、実装の詳細、変数とリテラル値のどちらについて話しているかによって異なります。

最初に理解しておくべきことは、Cに負の整数リテラルがないことです。「-2147483648」は、単項マイナス演算とそれに続く正の整数リテラルです。

Intとlongが32ビットでlong longが64ビットである典型的な32ビットプラットフォームで実行していると仮定して、式を考えてみましょう。

(-(-2147483648)== -2147483648)

コンパイラーは2147483648を保持できる型を見つける必要があります。適合C99コンパイラーでは「long long」型を使用しますが、C90コンパイラーは「unsigned long」型を使用できます。

コンパイラがlong long型を使用する場合、オーバーフローは発生せず、比較はfalseです。コンパイラがunsigned longを使用する場合、unsigned wraparoundルールが作用し、比較はtrueになります。

0
plugwash