85

2 つの符号なしバイトbx. bsubasb - xbaddasを計算する必要がありb + xます。ただし、これらの操作中にアンダーフロー/オーバーフローが発生することは望ましくありません。例(疑似コード):

b = 3; x = 5;
bsub = b - x; // bsub must be 0, not 254

b = 250; x = 10;
badd = b + x; // badd must be 255, not 4

これを行う明白な方法には、分岐が含まれます。

bsub = b - min(b, x);
badd = b + min(255 - b, x);

これを行うためのより良い方法があるかどうか、つまり、ハッキーなビット操作があるかどうか疑問に思っていますか?

4

11 に答える 11

87

Branchfree Saturating Arithmeticの記事では、このための戦略が説明されています。

それらの追加ソリューションは次のとおりです。

u32b sat_addu32b(u32b x, u32b y)
{
    u32b res = x + y;
    res |= -(res < x);

    return res;
}

uint8_t 用に変更:

uint8_t  sat_addu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x + y;
    res |= -(res < x);

    return res;
}

そしてそれらの減算解は次のとおりです。

u32b sat_subu32b(u32b x, u32b y)
{
    u32b res = x - y;
    res &= -(res <= x);

    return res;
}

uint8_t 用に変更:

uint8_t sat_subu8b(uint8_t x, uint8_t y)
{
    uint8_t res = x - y;
    res &= -(res <= x);

    return res;
}
于 2015-11-02T16:17:36.840 に答える
40

簡単な方法は、オーバーフローを検出し、それに応じて値をリセットすることです。

bsub = b - x;
if (bsub > b)
{
    bsub = 0;
}

badd = b + x;
if (badd < b)
{
    badd = 255;
}

-O2 を指定してコンパイルする場合、GCC はオーバーフロー チェックを条件付き割り当てに最適化できます。

他のソリューションと比較して、どの程度最適化されているかを測定しました。私のPCで1000000000以上の操作を行った場合、このソリューションと@ShafikYaghmourのソリューションは平均4.2秒、@chuxのソリューションは平均4.8秒でした。このソリューションも読みやすくなっています。

于 2015-11-02T15:50:29.343 に答える
16

減算の場合:

diff = (a - b)*(a >= b);

添加:

sum = (a + b) | -(a > (255 - b))

進化

// sum = (a + b)*(a <= (255-b)); this fails
// sum = (a + b) | -(a <= (255 - b)) falis too

@R_Kappに感謝

@NathanOliverに感謝

この演習では、単純なコーディングの価値を示します。

sum = b + min(255 - b, a);
于 2015-11-02T15:45:06.523 に答える
3

追加の場合:

unsigned temp = a+b;  // temp>>8 will be 1 if overflow else 0
unsigned char c = temp | -(temp >> 8);

減算の場合:

unsigned temp = a-b;  // temp>>8 will be 0xFF if neg-overflow else 0
unsigned char c = temp & ~(temp >> 8);

比較演算子や乗算は必要ありません。

于 2015-11-02T20:33:23.650 に答える
3

アセンブリまたは組み込み関数を使用する意思がある場合は、最適なソリューションがあると思います。

減算の場合:

命令を使用できますsbb

MSVC では、組み込み関数_subborrow_u64を使用できます (他のビット サイズでも使用できます)。

使用方法は次のとおりです。

// *c = a - (b + borrow)
// borrow_flag is set to 1 if (a < (b + borrow))
borrow_flag = _subborrow_u64(borrow_flag, a, b, c);

あなたの状況に適用する方法は次のとおりです

uint64_t sub_no_underflow(uint64_t a, uint64_t b){
    uint64_t result;
    borrow_flag = _subborrow_u64(0, a, b, &result);
    return result * !borrow_flag;
}

追加の場合:

命令を使用できますadcx

MSVC では、組み込み関数_addcarry_u64 (他のビット サイズでも利用可能) を使用できます。

使用方法は次のとおりです。

// *c = a + b + carry
// carry_flag is set to 1 if there is a carry bit
carry_flag = _addcarry_u64(carry_flag, a, b, c);

あなたの状況に適用する方法は次のとおりです

uint64_t add_no_overflow(uint64_t a, uint64_t b){
    uint64_t result;
    carry_flag = _addcarry_u64(0, a, b, &result);
    return !carry_flag * result - carry_flag;
}

これは減算ほど好きではありませんが、かなり気の利いたものだと思います。

加算がオーバーフローした場合、carry_flag = 1. Not-ingcarry_flagは 0 を返すため!carry_flag * result = 0、オーバーフローがある場合。は符号なし整数値をその最大値に設定するため0 - 1、関数はキャリーがない場合は加算の結果を返し、キャリーがある場合は選択された整数値の最大値を返します。

于 2015-11-04T16:57:56.957 に答える
2

これを 2 バイトで行う場合は、可能な限り単純なコードを使用してください。

200 億バイトでこれを行う場合は、プロセッサで使用できるベクトル命令と、それらが使用できるかどうかを確認してください。お使いのプロセッサが 1 つの命令でこれらの操作を 32 回実行できる場合があります。

于 2015-11-03T01:44:31.087 に答える
2

Boost Library Incubatorで安全な数値ライブラリを使用することもできます。int、long などのドロップイン置換を提供します。これにより、検出されないオーバーフローやアンダーフローなどが発生しないことが保証されます。

于 2015-11-02T16:01:23.100 に答える
1

これはどうですか:

bsum = a + b;
bsum = (bsum < a || bsum < b) ? 255 : bsum;

bsub = a - b;
bsub = (bsub > a || bsub > b) ? 0 : bsub;
于 2015-11-02T15:45:07.137 に答える
1

これらのメソッドを頻繁に呼び出す場合、最速の方法はビット操作ではなく、おそらくルックアップ テーブルです。操作ごとに長さ 511 の配列を定義します。マイナス(引き算)の例

static unsigned char   maxTable[511];
memset(maxTable, 0, 255);           // If smaller, emulates cutoff at zero
maxTable[255]=0;                    // If equal     - return zero
for (int i=0; i<256; i++)
    maxTable[255+i] = i;            // If greater   - return the difference

配列は静的で、一度だけ初期化されます。これで、減算をインライン メソッドとして、またはプリコンパイラを使用して定義できます。

#define MINUS(A,B)    maxTable[A-B+255];

使い方?unsigned char のすべての可能な減算を事前に計算したいとします。結果は -255 から +255 まで変化し、合計 511 通りの結果が得られます。すべての可能な結果の配列を定義しますが、C では負のインデックスからアクセスできないため、+255 ([A-B+255] 内) を使用します。配列の中心へのポインターを定義することにより、このアクションを削除できます。

const unsigned char *result = maxTable+255;
#define MINUS(A,B)    result[A-B];

次のように使用します。

bsub  = MINUS(13,15); // i.e 13-15 with zero cutoff as requested

実行が非常に高速であることに注意してください。結果を得るために、1 つの減算と 1 つのポインター参照のみが必要です。分岐なし。静的配列は非常に短いため、計算をさらに高速化するために CPU のキャッシュに完全にロードされます。

追加でも同じことが機能しますが、テーブルが少し異なります (最初の 256 要素はインデックスになり、最後の 255 要素は 255 に等しくなり、255 を超えるカットオフをエミュレートします。

ビット操作に固執するなら、(a>b) を使用する答えは間違っています。これはまだ分岐として実装される可能性があります。サインビット手法を使用する

// (num1>num2) ? 1 : 0
#define        is_int_biggerNotEqual( num1,num2) ((((__int32)((num2)-(num1)))&0x80000000)>>31)

これで、減算と加算の計算に使用できます。

分岐を使用せずに関数 max()、min() をエミュレートする場合:

inline __int32 MIN_INT(__int32 x, __int32 y){   __int32 d=x-y; return y+(d&(d>>31)); }              

inline __int32 MAX_INT(__int32 x, __int32 y){   __int32 d=x-y; return x-(d&(d>>31)); }

上記の例では、32 ビット整数を使用しています。64 に変更することもできますが、32 ビットの計算の方が少し高速に実行されると思います。君による

于 2015-11-02T17:31:38.753 に答える