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


100

結果が負になる場合に、同じ型の別の整数から符号なし整数を減算する問題があると信じているように見える誰かからコードを見つけました。したがって、このようなコードは、たまたまほとんどのアーキテクチャで機能しても、正しくありません。

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)

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


3
標準での単語の選択は残念です。「決してオーバーフローしない」ということは、エラー状態ではないことを意味します。「wraps」という値をオーバーフローさせる代わりに、標準で用語を使用します。
danorton、2011

回答:


107

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

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

ご覧のとおり、(unsigned)0 - (unsigned)1UMOD_MAX + 1を法とする-1、つまりUINT_MAXです。

「符号なしのオペランドを含む計算は決してオーバーフローすることはできない」と書いてありますが、上限を超えた場合にのみ適用されると思われるかもしれませんが、これは文の実際の結合部分の動機として提示されています: "a結果の符号なし整数型で表すことができない結果は、結果の型で表すことができる最大値よりも1大きい数を法として減じられます。」この句は、型の上限のオーバーフローに限定されず、表現するには低すぎる値にも等しく適用されます。


2
ありがとうございました!私は今、私が欠けていた解釈を見ます。彼らはもっと明確な言葉遣いを選んだかもしれないと思う。

4
私はゼロに周りの任意の符号なし加算ロールとは、騒乱を起こした場合ので、それが可能になることを知って、今とても気分が良くuint、常に数学的な表現することを意図していたリングの整数の0スルーをUINT_MAX追加し、乗算剰余の操作で、UINT_MAX+1、およびないので、オーバーフローの。ただし、リングがそのような基本的なデータ型である場合、言語が他のサイズのリングに対するより一般的なサポートを提供しない理由を尋ねます。
Theodore Murdock

2
@TheodoreMurdockその質問への答えは簡単だと思います。私が知る限り、それがリングであるという事実は結果ではなく、原因です。実際の要件は、符号なしの型では、すべてのビットが値の表現に関与している必要があることです。そこからリング状の振る舞いが自然に流れてきます。他のタイプのそのような動作が必要な場合は、計算を行ってから、必要な係数を適用します。基本的な演算子を使用します。
underscore_d

@underscore_dもちろん...なぜ彼らが設計を決定したのかは明らかです。この設計の選択により、プログラマーがオーバーとアンダーを慎重に回避する必要がないように、まるで「データ型がリングとしてスペックされているため、算術オーバー/アンダーフローがない」としてスペックをおおかに書いたのは面白いだけです。 -flowまたはそれらのプログラムが見事に失敗するようにします。
セオドアマードック2017年

120

あなたがで作業する場合、符号なしのタイプ、モジュラー算術(としても知られている「ラップアラウンド」動作)が行われています。このモジュラー演算を理解するには、これらのクロックを見てください。

ここに画像の説明を入力してください

9 + 4 = 113 mod 12)なので、他の方向では1-4 = 9-3 mod 12)になります。符号なしの型を操作するときも同じ原則が適用されます。結果タイプがの場合、unsignedモジュラー演算が実行されます。


次に、結果をとして保存する次の操作を見てください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ます。数値の違いを取得し、モジュラー演算が適用されないようにする場合は、でabs()定義されている関数の使用を検討する必要がありますstdlib.h

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)
    // ...

だが

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

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

4
立証がこれを正しい答えにするけれども、時計を備えた素晴らしいもの。質問の前提には、これがすべて真実である可能性があるという主張がすでに含まれています。
オービットの

5
@LightnessRacesinOrbit:ありがとう。誰かがとても役に立ったと思うので、私はそれを書きました。それは完全な答えではないことに同意します。
LihO 2013

4
int d = abs(five - seven);がダメです。最初five - sevenに計算されます:昇格はオペランドの型をのままunsigned intにし、結果はを法として計算(UINT_MAX+1)され、に評価されUINT_MAX-1ます。次に、この値はへの実際のパラメータabsです。これは悪いニュースです。 abs(int)引数が範囲内になく、abs(long long)おそらく値を保持できるため、引数を渡すと未定義の動作が発生しますが、戻り値が強制的intに初期化されると、未定義の動作が発生しますd
Ben Voigt、2015年

1
@LihO:状況依存で、その結果の使用方法に応じて動作が異なるC ++の唯一の演算子は、カスタム変換演算子operator T()です。ここで説明する2つの式の加算はunsigned int、オペランドの型に基づいてtype で実行されます。加算の結果はunsigned intです。次に、その結​​果は、コンテキストで必要な型に暗黙的に変換されます。変換は、値が新しい型で表現できないため失敗します。
Ben Voigt、2015年

1
@LihO:double x = 2/3;vs について考えると役立つかもしれませんdouble y = 2.0/3;
Ben Voigt

5

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

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

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

たとえば、この評価

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

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


4
32ビットではなく、16ビットの符号なしの型を意味すると思います。
xioxox 2015年

4

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.