web-dev-qa-db-ja.com

ラップアラウンドまたはオーバーフローで2つの符号なし整数を減算する方法

減算する必要がある2つの符号なし整数(xとy)があります。 xは常にyよりも大きい。ただし、xとyはどちらもラップアラウンドできます。たとえば、両方がバイトの場合、0xffの後に0x00が来ます。問題のケースは、xが回り込む場合、yは回り込む場合です。ここで、xはyよりも小さいように見えます。幸い、xは2回ラップしません(1回だけが保証されます)。バイトを想定すると、xはラップされて0x2になりましたが、yはラップされておらず、0xFEです。 x-yの正解は0x4であるはずです。

多分、

( x > y) ? (x-y) : (x+0xff-y);

しかし、私は別の方法があると思います、2の補数を含む何かですか?そしてこの組み込みシステムでは、xとyは最大のunsigned int型なので、0xff ...を追加することはできません

ステートメントを記述する最良の方法は何ですか(ターゲット言語はC)。

19
mgag

2つのnsigned整数を仮定:

  • 一方が他方よりも「大きい」ことになっていることがわかっている場合は、減算してください。あなたが2回以上ラップしていなければ動作します(明らかに、あなたが持っている場合、あなたは知ることができません)。
  • 一方が他方よりも大きいことがわからない場合は、減算して、同じ幅の符号付き整数に結果をキャストします。 2つの違いがsigned intの範囲内にある場合は機能します(そうでない場合はわかりません)。

明確にするために:元のポスターで説明されているシナリオは混乱を招くようですが、ハードウェアティックカウンターやプロトコルのシーケンス番号など、固定幅のカウンターが単調に増加するのが一般的です。カウンタが(たとえば8ビットの場合)0xfc、0xfd、0xfe、0xff、0x00、0x01、0x02、0x03などになり、xとyの2つの値のうち、xが後であることがわかります。 x == 0x02およびy == 0xfeの場合、2つのn-bit値の減算がモジュロ2をラップすると仮定して、x-y(8ビットの結果として)の計算は4の正しい答えを与えます。 -どのC99が符号なしの値の減算を保証するか。 (注:C標準ではnotsigned値の減算に対してこの動作を保証します。)

29

ここでは、「大きい」から「小さい」を差し引いたときに「うまくいく」理由をもう少し詳しく説明します。

これに入るいくつかの事…
1。ハードウェアでは、減算は加算を使用します。適切なオペランドは、加算される前に単に否定されます。
2。 2の補数(ほとんどすべてが使用)で、整数はすべてのビットを反転してから1を追加することによって否定されます。

ハードウェアは、これを上記の説明よりも効率的に実行しますが、これは減算の基本的なアルゴリズムです(値が符号なしの場合でも)。

したがって、8ビットの符号なし整数を使用して図2 – 250にしましょう。バイナリでは、

  0 0 0 0 0 0 1 0  
- 1 1 1 1 1 0 1 0

減算されるオペランドを否定してから加算します。否定するには、すべてのビットを反転してから1を追加することを思い出してください。2番目のオペランドのビットを反転した後、

0 0 0 0 0 1 0 1  

次に、1を追加した後、

0 0 0 0 0 1 1 0  

今、追加を実行します...

  0 0 0 0 0 0 1 0   
+ 0 0 0 0 0 1 1 0

= 0 0 0 0 1 0 0 0 = 8, which is the result we wanted from 2 - 250
20
Purdude

多分私は理解していませんが、何が間違っているのですか?

unsigned r = x - y;

13
GManNickG

述べられているように、質問は紛らわしいです。符号なしの値を減算しているとおっしゃいました。あなたが言ったように、xが常にyよりも大きい場合、x - yは、ラップアラウンドまたはオーバーフローすることはできません。だからあなたはただx - y(必要な場合)、それだけです。

3
AnT

これは、循環バッファの空き容量を決定したり、スライディングウィンドウフロー制御を行ったりするための効率的な方法です。 headtailには_unsigned int_ sを使用してください-それらをインクリメントしてラップしてください!バッファー長は2の累乗でなければなりません。

free = ((head - tail) & size_mask)、ここで_size_mask_は2 ^ n-1のバッファーまたはウィンドウサイズです。

2
C guy

問題は次のように述べられるべきです:

クロックの2つのポインターabの位置(角度)がuint8_tで指定されていると仮定します。全体の状況は、uint8_tの256の値に分割されます。 2つのポインター間の短い距離を効率的に計算するにはどうすればよいですか?

解決策は次のとおりです。

uint8_t smaller_distance = abs( (int8_t)( a - b ) );

さもなければabs()よりも効率的なものがあるので、私にはこれ以上効率的なものはないと思います。

1
hpc64

すでに正しい答えをコードに入れるだけです:

Xが小さい方の値であることがわかっている場合は、次の計算が機能します。

int main()
{
    uint8_t x = 0xff;
    uint8_t y = x + 20;
    uint8_t res = y - x;
    printf("Expect 20: %d\n", res); // res is 20

    return 0;
}

どちらが小さいかわからない場合:

int main()
{
    uint8_t x = 0xff;
    uint8_t y = x + 20;
    int8_t res1 = (int8_t)x - y;
    int8_t res2 = (int8_t)y - x;
    printf("Expect -20 and 20: %d and %d\n", res1, res2);

    return 0;
}

この場合、違いはuint8_tの範囲内でなければなりません。

コードの実験は、ソリューションをよりよく理解するのに役立ちました。

0
Tarion

他の全員の返信をエコーするには、2つを差し引いて、結果を符号なしと解釈すれば問題ありません。

明示的な反例がない限り。

x = 0x2y= 0x14の例は0x4ではなく、0xEEになります。

0
MSN