web-dev-qa-db-ja.com

符号なし整数減算は動作を定義していますか?

結果が負の場合に、同じタイプの別の整数から符号なし整数を減算するのに問題があると思われるコードを見つけました。そのため、このようなコードは、ほとんどのアーキテクチャで動作する場合でも正しくありません。

unsigned int To, Tf;

To = getcounter();
while (1) {
    Tf = getcounter();
    if ((Tf-To) >= TIME_LIMIT) {
        break;
    } 
}

これは、私が見つけることができるC標準からの漠然と関連した唯一の引用です。

結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値より1大きい数でモジュロ化されるため、符号なしオペランドを含む計算は決してオーバーフローしません。

その引用符を使用して、右側のオペランドが大きい場合、モジュロ切り捨てられた数値のコンテキストで意味を持つように演算が調整されることを意味すると思います。

つまり.

0x0000-0x0001 == 0x 1 0000-0x0001 == 0xFFFF

実装依存の署名されたセマンティクスを使用するのとは対照的に:

0x0000-0x0001 ==(符号なし)(0 + -1)==(0xFFFFだけでなく0xFFFEまたは0x8001)

どちらの解釈が正しいか?それはまったく定義されていますか?

90
jbcreix

符号なしの型で負の数を生成する減算の結果は、明確に定義されています。

  1. [...]結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値よりも1大きい数でモジュロ化されるため、符号なしオペランドを含む計算は決してオーバーフローしません。 (ISO/IEC 9899:1999(E)§6.2.5/ 9)

ご覧のとおり、(unsigned)0 - (unsigned)1は-1モジュロUINT_MAX + 1、つまりUINT_MAXに等しくなります。

「符号なしのオペランドを含む計算はオーバーフローすることはありません」と言うので、上限を超える場合にのみ適用されると思われるかもしれませんが、実際のバインディングでは動機として表示されます文の一部:「結果の符号なし整数型で表現できない結果は、結果の型で表現できる最大値よりも1大きい数を法として減少します。」このフレーズは、型の上限のオーバーフローに制限されず、表現するには低すぎる値にも同様に適用されます。

98
bdonlan

unsigned型を使用する場合、 modular Math「ラップアラウンド」動作とも呼ばれる)が実行されています。このモジュラー演算を理解するには、これらのクロックを見てください。

enter image description here

9 + 4 = 113 mod 12)、したがって、他の方向へ:1-4 = 9-3 mod 12)。符号なしの型を操作する際にも同じ原則が適用されます。 result typeunsignedの場合、モジュラー演算が行われます。


次に、結果をunsigned intとして保存する次の操作を見てください。

unsigned int five = 5, seven = 7;
unsigned int a = five - seven;      // a = (-2 % 2^32) = 4294967294 

int one = 1, six = 6;
unsigned int b = one - six;         // b = (-5 % 2^32) = 4294967291

結果がsignedであることを確認したい場合は、signed変数に格納するか、signedにキャストします。数値の差を取得し、モジュラー演算が適用されないようにする場合は、stdlib.hで定義されている abs() 関数の使用を検討する必要があります。

int c = five - seven;       // c = -2
int d = abs(five - seven);  // d =  2

特に条件を記述するときは、次の理由から非常に注意してください。

if (abs(five - seven) < seven)  // = if (2 < 7)
    // ...

if (five - seven < -1)          // = if (-2 < -1)
    // ...

if (one - six < 1)              // = if (-5 < 1)
    // ...

if ((int)(five - seven) < 1)    // = if (-2 < 1)
    // ...

but

if (five - seven < 1)   // = if ((unsigned int)-2 < 1) = if (4294967294 < 1)
    // ...

if (one - six < five)   // = if ((unsigned int)-5 < 5) = if (4294967291 < 5)
    // ...
109
LihO

unsigned int以上の型の符号なし数値では、型変換がない場合、a-bは、bに追加するとaを生成する符号なし数値を生成するものとして定義されます。 。負の数から符号なしへの変換は、符号反転された元の数に追加されたときにゼロを生成する数を生成することとして定義されます(したがって、-5を符号なしに変換すると、5に追加されたときにゼロを生成する値を生成します) 。

unsigned intより小さい符号なしの数値は、減算の前にint型に昇格される場合があり、a-bの動作はintのサイズに依存することに注意してください。

3
supercat

まあ、最初の解釈は正しいです。ただし、このコンテキストでの「署名されたセマンティクス」についてのあなたの推論は間違っています。

繰り返しますが、最初の解釈は正しいです。符号なし算術は、モジュロ算術の規則に従います。つまり、0x0000 - 0x0001は、32ビットの符号なし型に対して0xFFFFと評価されます。

ただし、同じ結果を生成するには、2番目の解釈(「署名されたセマンティクス」に基づく解釈)も必要です。つまり署名済みタイプのドメインで0 - 1を評価し、中間結果として-1を取得した場合でも、後で-1は、後で符号なしに変換されるときに0xFFFFを生成するために必要ですタイプ。一部のプラットフォームが符号付き整数(1の補数、符号付きの大きさ)にエキゾチックな表現を使用している場合でも、このプラットフォームは符号付き整数値を符号なしの値に変換するときにモジュロ演算のルールを適用する必要があります。

たとえば、この評価

signed int a = 0, b = 1;
unsigned int c = a - b;

プラットフォームが符号付き整数にエキゾチックな表現を使用している場合でも、cUINT_MAXを生成することが保証されています。

3
AnT