web-dev-qa-db-ja.com

符号なしと符号付きを減算した後、なぜ符号が異なるのですか?

unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;

cの値が正であるのにeの値が負であるのはなぜですか?

41
Eugene Kolombet

t - dの結果を分析することから始めましょう。

tunsigned intであるが、dintであるため、算術を行うために、dの値はunsigned intに変換されます。 (C++の規則では、ここではunsignedが優先されます)。したがって、10u - 16uを取得します(32ビットintと仮定)、4294967290uにラップアラウンドします。

この値は、最初の宣言でfloatに変換され、2番目の宣言でintに変換されます。

float(32ビット単精度IEEE)の典型的な実装を想定すると、その表現可能な最大値はおおよそ1e38であるため、4294967290uは十分にその範囲内にあります。丸めエラーが発生しますが、floatへの変換はオーバーフローしません。

intの場合、状況は異なります。 4294967290uは大きすぎてintに収まらないため、ラップアラウンドが発生し、値-6に戻ります。このようなラップアラウンドは標準では保証されていないことに注意してください。この場合の結果値は実装定義です(1)、つまり、結果の値はコンパイラ次第ですが、ドキュメント化する必要があります。


(1) C++ 17(N4659)、[conv.integral] 7.8/3:

宛先タイプが署名されている場合、値は宛先タイプで表現できる場合は変更されません。それ以外の場合、値は実装定義です。

64
Angew

まず、 "通常の算術変換" を理解する必要があります(リンクはC向けですが、C++のルールは同じです)。 C++では、混合型を使用して算術演算を行う場合(可能な場合は回避する必要があります)、どのタイプの計算を実行するかを決定する一連のルールがあります。

あなたの場合、符号なし整数から符号付き整数を引きます。プロモーションルールでは、実際の計算はunsigned intを使用して行われます。

したがって、計算は10 - 16 unsigned int算術です。符号なし算術はモジュロ算術です。つまり、ラップアラウンドします。したがって、一般的な32ビット整数を想定すると、この計算の結果は2 ^ 32-6です。

これは両方の行で同じです。減算は割り当てから完全に独立していることに注意してください。左側のタイプは、計算方法にまったく影響しません。左側の型が何らかの形で計算に影響を与えると考えるのは一般的な初心者の間違いです。ただし、float f = 5 / 6はゼロです。これは、除算が整数演算を使用するためです。

違いは、割り当て中に何が起こるかです。減算の結果は、ある場合にはfloatに、別の場合にはintに暗黙的に変換されます。

Floatへの変換は、型が表すことができる実際の値に最も近い値を見つけようとします。これは非常に大きな値になります。しかし、元の減算がもたらしたものではありません。

Intへの変換では、値がintの範囲に収まる場合、値は変更されません。ただし、2 ^ 32-6は、32ビットintが保持できる2 ^ 31-1よりもはるかに大きいため、変換ルールの他の部分を取得します。これは、結果の値が実装定義であることを示します。これは標準の用語であり、「異なるコンパイラは異なることを実行できますが、実行内容を文書化する必要があります」。

すべての実用的な目的で、遭遇する可能性のあるすべてのコンパイラは、ビットパターンは同じままであり、符号付きとして解釈されると言います。 2の補数演算の方法(ほとんどすべてのコンピューターが負の数を表す方法)のため、結果は計算から期待される-6になります。

しかし、これはすべて、最初のポイントを繰り返す非常に長い方法であり、「混合型の算術演算を行わない」です。最初に明示的に型をキャストし、正しいことを行うことがわかっている型にキャストします。

15
Sebastian Redl