web-dev-qa-db-ja.com

ビットシフトを使用して10で除算しますか?

純粋なビットシフト、加算、減算、および多分乗算を使用して、符号なし整数を10で除算することは可能ですか?リソースが非常に限られており、分割が遅いプロセッサを使用する。

40
Thomas O

これは、Microsoftコンパイラーが小さな整数定数で除算をコンパイルするときに行うことです。 32ビットマシンを想定(コードは適宜調整可能):

int32_t div10(int32_t dividend)
{
    int64_t invDivisor = 0x1999999A;
    return (int32_t) ((invDivisor * dividend) >> 32);
}

ここで行っているのは、1/10 * 2 ^ 32の近似値を乗算し、2 ^ 32を削除することです。このアプローチは、異なる除数と異なるビット幅に適応できます。

これはia32アーキテクチャに最適です。IMUL命令により64ビット製品がedx:eaxに入れられ、edx値が必要な値になるためです。 Viz(配当がeaxで渡され、商がeaxで返されると仮定)

div10 proc 
    mov    edx,1999999Ah    ; load 1/10 * 2^32
    imul   eax              ; edx:eax = dividend / 10 * 2 ^32
    mov    eax,edx          ; eax = dividend / 10
    ret
    endp

乗算命令が遅いマシンでも、これはソフトウェア除算よりも高速です。

55
John Källén

ここまでの回答は実際の質問と一致していますが、タイトルとは一致していません。そこで、実際にビットシフトのみを使用する Hacker's Delight に大きな影響を受けたソリューションを紹介します。

unsigned divu10(unsigned n) {
    unsigned q, r;
    q = (n >> 1) + (n >> 2);
    q = q + (q >> 4);
    q = q + (q >> 8);
    q = q + (q >> 16);
    q = q >> 3;
    r = n - (((q << 2) + q) << 1);
    return q + (r > 9);
}

これは、乗算命令を持たないアーキテクチャに最適なソリューションだと思います。

30
realtime

もちろん、精度をいくらか落として生きることができれば可能です。入力値の値の範囲がわかっている場合、ビットシフトと乗算を正確に行うことができます。このブログで説明されているように、10、60、...で除算する方法のいくつかの例- 時間は最も速い方法 可能です。

temp = (ms * 205) >> 11;  // 205/2048 is nearly the same as /10
15
Alois Kraus

Kuba Oberの反応を考えると、同じ脈に別の反応があります。結果の反復近似を使用しますが、驚くべきパフォーマンスは期待できません。

x where x = v / 10を見つける必要があるとしましょう。

逆の操作v = x * 10を使用します。これは、x = a + bのときにx * 10 = a * 10 + b * 10になるNiceプロパティがあるためです。

これまでの結果の最良の近似を保持する変数としてxを使用してみましょう。検索が終了すると、xが結果を保持します。 b of xの各ビットを、最上位から最下位まで1つずつ設定し、(x + b) * 10vを比較します。 v以下の場合、bのビットxが設定されます。次のビットをテストするには、bを1つ右にシフトします(2で割る)。

他の変数にx * 10およびb * 10を保持することにより、10の乗算を回避できます。

これにより、vを10で除算する次のアルゴリズムが生成されます。

uin16_t x = 0, x10 = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
    uint16_t t = x10 + b10;
    if (t <= v) {
        x10 = t;
        x |= b;
    }
    b10 >>= 1;
    b >>= 1;
}
// x = v / 10

編集:変数x10の必要性を回避するKuba Oberのアルゴリズムを取得するには、代わりにvおよびb10からv10を減算します。この場合、x10はもう必要ありません。アルゴリズムは

uin16_t x = 0, b = 0x1000, b10 = 0xA000;
while (b != 0) {
    if (b10 <= v) {
        v -= b10;
        x |= b;
    }
    b10 >>= 1;
    b >>= 1;
}
// x = v / 10

ループは巻き戻され、bb10の異なる値が定数として事前計算される場合があります。

3
chmike

よく除算は減算なので、はい。 1だけ右にシフト(2で除算)。次に、結果から5を減算し、値が5未満になるまで減算を行った回数をカウントします。結果は、行った減算の数です。ああ、おそらく分割はより速くなるだろう。

通常の除算を使用して右にシフトしてから5で除算するハイブリッド戦略では、除算器のロジックがまだこれを行っていない場合、パフォーマンスが向上する可能性があります。

2
tvanfosson

一度に1か所しか移動できないアーキテクチャでは、2のべき乗に10を掛けたものに対する一連の明示的な比較が、ハッカーが喜ぶソリューションよりもうまく機能する可能性があります。 16ビットの配当を想定:

uint16_t div10(uint16_t dividend) {
  uint16_t quotient = 0;
  #define div10_step(n) \
    do { if (dividend >= (n*10)) { quotient += n; dividend -= n*10; } } while (0)
  div10_step(0x1000);
  div10_step(0x0800);
  div10_step(0x0400);
  div10_step(0x0200);
  div10_step(0x0100);
  div10_step(0x0080);
  div10_step(0x0040);
  div10_step(0x0020);
  div10_step(0x0010);
  div10_step(0x0008);
  div10_step(0x0004);
  div10_step(0x0002);
  div10_step(0x0001);
  #undef div10_step
  if (dividend >= 5) ++quotient; // round the result (optional)
  return quotient;
}
2
Kuba Ober

アロイスの答えを少し拡張するために、提案されたy = (x * 205) >> 11をさらにいくつかの倍数/シフトに拡張できます:

_y = (ms *        1) >>  3 // first error 8
y = (ms *        2) >>  4 // 8
y = (ms *        4) >>  5 // 8
y = (ms *        7) >>  6 // 19
y = (ms *       13) >>  7 // 69
y = (ms *       26) >>  8 // 69
y = (ms *       52) >>  9 // 69
y = (ms *      103) >> 10 // 179
y = (ms *      205) >> 11 // 1029
y = (ms *      410) >> 12 // 1029
y = (ms *      820) >> 13 // 1029
y = (ms *     1639) >> 14 // 2739
y = (ms *     3277) >> 15 // 16389
y = (ms *     6554) >> 16 // 16389
y = (ms *    13108) >> 17 // 16389
y = (ms *    26215) >> 18 // 43699
y = (ms *    52429) >> 19 // 262149
y = (ms *   104858) >> 20 // 262149
y = (ms *   209716) >> 21 // 262149
y = (ms *   419431) >> 22 // 699059
y = (ms *   838861) >> 23 // 4194309
y = (ms *  1677722) >> 24 // 4194309
y = (ms *  3355444) >> 25 // 4194309
y = (ms *  6710887) >> 26 // 11184819
y = (ms * 13421773) >> 27 // 67108869
_

各行は単一の独立した計算であり、コメントに示されている値で最初の「エラー」/不正な結果が表示されます。一般に、計算で中間値を保存するために必要な余分なビットを最小限に抑えるため、特定のエラー値に対して最小のシフトを取ることをお勧めします。 _(x * 13) >> 7_は、オーバーヘッドが2ビット少ないので、_(x * 52) >> 9_よりも「良い」一方で、両方とも68を超える間違った答えを出し始めます。

これらをさらに計算する場合は、次の(Python)コードを使用できます。

_def mul_from_shift(shift):
    mid = 2**shift + 5.
    return int(round(mid / 10.))
_

そして、この近似がうまくいかなくなったときに計算するための明白なことをしました:

_def first_err(mul, shift):
    i = 1
    while True:
        y = (i * mul) >> shift
        if y != i // 10:
            return i
        i += 1
_

(_//_は「整数」除算に使用されます。つまり、ゼロに切り捨てられます/丸められます)

エラーの「3/1」パターンの理由(つまり、8が3回繰り返され、9が続く)は、ベースの変更によるものと思われます。つまり、log2(10)は〜3.32です。エラーをプロットすると、次の結果が得られます。

errors

ここで、相対誤差はmul_from_shift(shift) / (1<<shift) - 0.1で与えられます

2
Sam Mason