web-dev-qa-db-ja.com

128ビット整数を64ビット整数を法として計算する最速の方法

128ビットの符号なし整数Aと64ビットの符号なし整数Bがあります。A % Bを計算する最速の方法は何ですか。これは、AをBで除算したときの(64ビット)剰余です。

これをCまたはアセンブリ言語のいずれかで実行したいと考えていますが、32ビットx86プラットフォームをターゲットにする必要があります。これは残念なことに、128ビット整数のコンパイラサポートや、1つの命令で必要な操作を実行するx64アーキテクチャの機能を利用できないことを意味します。

編集:

これまでの回答ありがとうございます。しかし、提案されたアルゴリズムはかなり遅いようです-128ビット64ビット除算を実行する最速の方法は、64ビット32ビット除算に対するプロセッサのネイティブサポートを活用することではないでしょうか。いくつかの小さな部門で大きな部門を実行する方法があるかどうか誰かが知っていますか?

Re:Bはどのくらいの頻度で変化しますか?

主に私は一般的なソリューションに興味があります-AとBが毎回異なる可能性がある場合、どのような計算を実行しますか?

ただし、2番目に考えられる状況は、BがAほど頻繁に変化しないことです。各Bで除算されるのは200もの場合があります。この場合、答えはどのように異なりますか?

53
user200783

Russian Peasant Multiplication の除算バージョンを使用できます。

残りを見つけるには、(疑似コードで)実行します。

X = B;

while (X <= A/2)
{
    X <<= 1;
}

while (A >= B)
{
    if (A >= X)
        A -= X;
    X >>= 1;
}

係数はAのままです。

64ビットの数値のペアで構成される値を操作するには、シフト、比較、および減算を実装する必要がありますが、それはかなり自明です(おそらく、左シフト1をX + Xとして実装する必要があります)。 。

これは最大で255回ループします(128ビットA)。もちろん、ゼロ除数の事前チェックを行う必要があります。

31
caf

おそらく、完成したプログラムを探しているかもしれませんが、多精度演算の基本的なアルゴリズムは、Knuthの Art of Computer Programming 、第2巻にあります。オンラインで説明されている除算アルゴリズムを見つけることができます ここ 。アルゴリズムは任意の多精度演算を処理するため、必要以上に一般的ですが、64ビットまたは32ビットの桁で行われる128ビット演算では、アルゴリズムを簡略化できるはずです。妥当な量の作業(a)アルゴリズムを理解し、(b)アルゴリズムをCまたはアセンブラーに変換する準備をしてください。

また、 Hacker's Delight もチェックしてみてください。これは、非常に巧妙なアセンブラーや他の低レベルのハッカー(多精度演算を含む)でいっぱいです。

13
Dale Hagglund

A = AH*2^64 + AL

A % B == (((AH % B) * (2^64 % B)) + (AL % B)) % B
      == (((AH % B) * ((2^64 - B) % B)) + (AL % B)) % B

コンパイラが64ビット整数をサポートしている場合、これがおそらく最も簡単な方法です。 MSVCの32ビットx86での64ビットモジュロの実装は、毛深いループで満たされたアセンブリ(VC\crt\src\intel\llrem.asm勇敢なため)、私は個人的にはそれで行きます。

11
MSN

これは、ほとんどテストされていない、部分的に速度が変更されたMod128by64「ロシアの農民」アルゴリズム関数です。残念ながら私はDelphiユーザーなので、この関数はDelphiで動作します。 :)しかし、アセンブラはほとんど同じなので...

function Mod128by64(Dividend: PUInt128; Divisor: PUInt64): UInt64;
//In : eax = @Dividend
//   : edx = @Divisor
//Out: eax:edx as Remainder
asm
//Registers inside rutine
//Divisor = edx:ebp
//Dividend = bh:ebx:edx //We need 64 bits + 1 bit in bh
//Result = esi:edi
//ecx = Loop counter and Dividend index
  Push    ebx                     //Store registers to stack
  Push    esi
  Push    edi
  Push    ebp
  mov     ebp, [edx]              //Divisor = edx:ebp
  mov     edx, [edx + 4]
  mov     ecx, ebp                //Div by 0 test
  or      ecx, edx                
  jz      @DivByZero
  xor     edi, edi                //Clear result
  xor     esi, esi
//Start of 64 bit division Loop
  mov     ecx, 15                 //Load byte loop shift counter and Dividend index
@SkipShift8Bits:                  //Small Dividend numbers shift optimisation
  cmp     [eax + ecx], ch         //Zero test
  jnz     @EndSkipShiftDividend
  loop    @SkipShift8Bits         //Skip 8 bit loop
@EndSkipShiftDividend:
  test    edx, $FF000000          //Huge Divisor Numbers Shift Optimisation
  jz      @Shift8Bits             //This Divisor is > $00FFFFFF:FFFFFFFF
  mov     ecx, 8                  //Load byte shift counter
  mov     esi, [eax + 12]         //Do fast 56 bit (7 bytes) shift...
  shr     esi, cl                 //esi = $00XXXXXX
  mov     edi, [eax + 9]          //Load for one byte right shifted 32 bit value
@Shift8Bits:
  mov     bl, [eax + ecx]         //Load 8 bits of Dividend
//Here we can unrole partial loop 8 bit division to increase execution speed...
  mov     ch, 8                   //Set partial byte counter value
@Do65BitsShift:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  setc    bh                      //Save 65th bit
  sub     edi, ebp                //Compare dividend and  divisor
  sbb     esi, edx                //Subtract the divisor
  sbb     bh, 0                   //Use 65th bit in bh
  jnc     @NoCarryAtCmp           //Test...
  add     edi, ebp                //Return privius dividend state
  adc     esi, edx
@NoCarryAtCmp:
  dec     ch                      //Decrement counter
  jnz     @Do65BitsShift
//End of 8 bit (byte) partial division loop
  dec     cl                      //Decrement byte loop shift counter
  jns     @Shift8Bits             //Last jump at cl = 0!!!
//End of 64 bit division loop
  mov     eax, edi                //Load result to eax:edx
  mov     edx, esi
@RestoreRegisters:
  pop     ebp                     //Restore Registers
  pop     edi
  pop     esi
  pop     ebx
  ret
@DivByZero:
  xor     eax, eax                //Here you can raise Div by 0 exception, now function only return 0.
  xor     edx, edx
  jmp     @RestoreRegisters
end;

少なくとももう1つの速度最適化が可能です! 「Huge Divisor Numbers Shift Optimization」の後、除数の上位ビットをテストできます。それが0の場合、追加のbhレジスタを65番目のビットとして使用して格納する必要はありません。したがって、ループの展開された部分は次のようになります。

  shl     bl,1                    //Shift dividend left for one bit
  rcl     edi,1
  rcl     esi,1
  sub     edi, ebp                //Compare dividend and  divisor
  sbb     esi, edx                //Subtract the divisor
  jnc     @NoCarryAtCmpX
  add     edi, ebp                //Return privius dividend state
  adc     esi, edx
@NoCarryAtCmpX:
8
GJ.

@cafが受け入れた回答は本当に素晴らしいものでしたが、何年も見られなかったバグが含まれています。

そのテストや他のソリューションをテストするために、私はテストハーネスを投稿し、コミュニティウィキにしています。

unsigned cafMod(unsigned A, unsigned B) {
  assert(B);
  unsigned X = B;
  // while (X < A / 2) {  Original code used <
  while (X <= A / 2) {
    X <<= 1;
  }
  while (A >= B) {
    if (A >= X) A -= X;
    X >>= 1;
  }
  return A;
}

void cafMod_test(unsigned num, unsigned den) {
  if (den == 0) return;
  unsigned y0 = num % den;
  unsigned y1 = mod(num, den);
  if (y0 != y1) {
    printf("FAIL num:%x den:%x %x %x\n", num, den, y0, y1);
    fflush(stdout);
    exit(-1);
  }
}

unsigned Rand_unsigned() {
  unsigned x = (unsigned) Rand();
  return x * 2 ^ (unsigned) Rand();
}

void cafMod_tests(void) {
  const unsigned i[] = { 0, 1, 2, 3, 0x7FFFFFFF, 0x80000000, 
      UINT_MAX - 3, UINT_MAX - 2, UINT_MAX - 1, UINT_MAX };
  for (unsigned den = 0; den < sizeof i / sizeof i[0]; den++) {
    if (i[den] == 0) continue;
    for (unsigned num = 0; num < sizeof i / sizeof i[0]; num++) {
      cafMod_test(i[num], i[den]);
    }
  }
  cafMod_test(0x8711dd11, 0x4388ee88);
  cafMod_test(0xf64835a1, 0xf64835a);

  time_t t;
  time(&t);
  srand((unsigned) t);
  printf("%u\n", (unsigned) t);fflush(stdout);
  for (long long n = 10000LL * 1000LL * 1000LL; n > 0; n--) {
    cafMod_test(Rand_unsigned(), Rand_unsigned());
  }

  puts("Done");
}

int main(void) {
  cafMod_tests();
  return 0;
}
4
chux

Mod128by64の「ロシアの農民」除算機能の両方のバージョンを作成しました:クラシックと速度を最適化しました。最適化された速度は、私の3Ghz PCで毎秒1000.000以上のランダム計算を実行でき、従来の関数より3倍以上高速です。 128 x 64の計算と64 x 64ビットのモジュロの計算の実行時間を比較すると、この関数は約50%遅くなります。

古典的なロシアの農民:

function Mod128by64Clasic(Dividend: PUInt128; Divisor: PUInt64): UInt64;
//In : eax = @Dividend
//   : edx = @Divisor
//Out: eax:edx as Remainder
asm
//Registers inside rutine
//edx:ebp = Divisor
//ecx = Loop counter
//Result = esi:edi
  Push    ebx                     //Store registers to stack
  Push    esi
  Push    edi
  Push    ebp
  mov     ebp, [edx]              //Load  divisor to edx:ebp
  mov     edx, [edx + 4]
  mov     ecx, ebp                //Div by 0 test
  or      ecx, edx
  jz      @DivByZero
  Push    [eax]                   //Store Divisor to the stack
  Push    [eax + 4]
  Push    [eax + 8]
  Push    [eax + 12]
  xor     edi, edi                //Clear result
  xor     esi, esi
  mov     ecx, 128                //Load shift counter
@Do128BitsShift:
  shl     [esp + 12], 1           //Shift dividend from stack left for one bit
  rcl     [esp + 8], 1
  rcl     [esp + 4], 1
  rcl     [esp], 1
  rcl     edi, 1
  rcl     esi, 1
  setc    bh                      //Save 65th bit
  sub     edi, ebp                //Compare dividend and  divisor
  sbb     esi, edx                //Subtract the divisor
  sbb     bh, 0                   //Use 65th bit in bh
  jnc     @NoCarryAtCmp           //Test...
  add     edi, ebp                //Return privius dividend state
  adc     esi, edx
@NoCarryAtCmp:
  loop    @Do128BitsShift
//End of 128 bit division loop
  mov     eax, edi                //Load result to eax:edx
  mov     edx, esi
@RestoreRegisters:
  lea     esp, esp + 16           //Restore Divisors space on stack
  pop     ebp                     //Restore Registers
  pop     edi                     
  pop     esi
  pop     ebx
  ret
@DivByZero:
  xor     eax, eax                //Here you can raise Div by 0 exception, now function only return 0.
  xor     edx, edx
  jmp     @RestoreRegisters
end;

速度を最適化したロシアの農民:

function Mod128by64Oprimized(Dividend: PUInt128; Divisor: PUInt64): UInt64;
//In : eax = @Dividend
//   : edx = @Divisor
//Out: eax:edx as Remainder
asm
//Registers inside rutine
//Divisor = edx:ebp
//Dividend = ebx:edx //We need 64 bits
//Result = esi:edi
//ecx = Loop counter and Dividend index
  Push    ebx                     //Store registers to stack
  Push    esi
  Push    edi
  Push    ebp
  mov     ebp, [edx]              //Divisor = edx:ebp
  mov     edx, [edx + 4]
  mov     ecx, ebp                //Div by 0 test
  or      ecx, edx
  jz      @DivByZero
  xor     edi, edi                //Clear result
  xor     esi, esi
//Start of 64 bit division Loop
  mov     ecx, 15                 //Load byte loop shift counter and Dividend index
@SkipShift8Bits:                  //Small Dividend numbers shift optimisation
  cmp     [eax + ecx], ch         //Zero test
  jnz     @EndSkipShiftDividend
  loop    @SkipShift8Bits         //Skip Compute 8 Bits unroled loop ?
@EndSkipShiftDividend:
  test    edx, $FF000000          //Huge Divisor Numbers Shift Optimisation
  jz      @Shift8Bits             //This Divisor is > $00FFFFFF:FFFFFFFF
  mov     ecx, 8                  //Load byte shift counter
  mov     esi, [eax + 12]         //Do fast 56 bit (7 bytes) shift...
  shr     esi, cl                 //esi = $00XXXXXX
  mov     edi, [eax + 9]          //Load for one byte right shifted 32 bit value
@Shift8Bits:
  mov     bl, [eax + ecx]         //Load 8 bit part of Dividend
//Compute 8 Bits unroled loop
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove0         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow0
  ja      @DividentAbove0
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow0
@DividentAbove0:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow0:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove1         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow1
  ja      @DividentAbove1
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow1
@DividentAbove1:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow1:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove2         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow2
  ja      @DividentAbove2
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow2
@DividentAbove2:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow2:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove3         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow3
  ja      @DividentAbove3
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow3
@DividentAbove3:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow3:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove4         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow4
  ja      @DividentAbove4
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow4
@DividentAbove4:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow4:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove5         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow5
  ja      @DividentAbove5
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow5
@DividentAbove5:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow5:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove6         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow6
  ja      @DividentAbove6
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow6
@DividentAbove6:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow6:
  shl     bl, 1                   //Shift dividend left for one bit
  rcl     edi, 1
  rcl     esi, 1
  jc      @DividentAbove7         //dividend hi bit set?
  cmp     esi, edx                //dividend hi part larger?
  jb      @DividentBelow7
  ja      @DividentAbove7
  cmp     edi, ebp                //dividend lo part larger?
  jb      @DividentBelow7
@DividentAbove7:
  sub     edi, ebp                //Return privius dividend state
  sbb     esi, edx
@DividentBelow7:
//End of Compute 8 Bits (unroled loop)
  dec     cl                      //Decrement byte loop shift counter
  jns     @Shift8Bits             //Last jump at cl = 0!!!
//End of division loop
  mov     eax, edi                //Load result to eax:edx
  mov     edx, esi
@RestoreRegisters:
  pop     ebp                     //Restore Registers
  pop     edi
  pop     esi
  pop     ebx
  ret
@DivByZero:
  xor     eax, eax                //Here you can raise Div by 0 exception, now function only return 0.
  xor     edx, edx
  jmp     @RestoreRegisters
end;
4
GJ.

32ビットコードを指定した質問は知っていますが、64ビットの回答は他の人にとって有用または興味深いかもしれません。

そして、はい、64b/32b => 32b除算は、128b%64b => 64bの有用なビルディングブロックになります。 libgccの___umoddi3_(以下にリンクされているソース)は、そのようなことを行う方法のアイデアを提供しますが、2N/N => N除算の上に2N%2N => 2Nのみを実装し、4N%2N =ではありません> 2N。

より幅広い多精度ライブラリが利用可能です。 https://gmplib.org/manual/Integer-Division.html#Integer-Division


64ビットマシンのGnu C___int128_ type を提供し、libgcc関数は乗算と除算を行います。ターゲットアーキテクチャで可能な限り効率的に。

x86-64の _div r/m64_ 命令は128b/64b => 64b除算を実行します(2番目の出力として剰余も生成します)が、商がオーバーフローするとエラーになります。そのため、_A/B > 2^64-1_の場合は直接使用できませんが、gccで使用できます(またはlibgccが使用するのと同じコードをインライン化することもできます)。


これは、( Godboltコンパイラエクスプローラ )を1つまたは2つのdiv命令にコンパイルします(これは libgcc 関数呼び出し内で発生します)。より速い方法があった場合、libgccはおそらく代わりにそれを使用します。

_#include <stdint.h>
uint64_t AmodB(unsigned __int128 A, uint64_t B) {
  return A % B;
}
_

関数が呼び出す___umodti3_関数は、完全な128b/128bモジュロを計算しますが、この関数の実装は、除数の上位半分が0であるという特殊なケースをチェックします libgccソースを参照 。 (libgccは、ターゲットアーキテクチャに応じて、そのコードから関数のsi/di/tiバージョンを構築します。 _udiv_qrnnd_ は、符号なし2N/N => Nを実行するインラインasmマクロですターゲットアーキテクチャの分割。

x86-64(およびハードウェア除算命令を備えた他のアーキテクチャ)の場合、高速パス(When high_half(A) < B; divがエラーにならないことを保証する場合)は、2つの分岐のみをとらない、いくつかの綿毛順序が狂ったCPUがかみ合うために、と単一の_div r64_命令、これは最新のx86 CPUで約50-100サイクルかかります Agner Fogのinsnテーブル によると、 divと並行して他の作業が発生する可能性がありますが、整数除算ユニットはあまりパイプライン化されず、divは多くのuopsにデコードします(FP除算とは異なります)。

フォールバックパスは、divが64ビットのみの場合、64ビットB命令を2つだけ使用しますが、_A/B_は64ビットに適合しないため、_A/B_は直接エラーになります。

Libgccの___umodti3_は___udivmoddi4_をラッパーにインライン化するだけで、残りを返すだけであることに注意してください。


同じBによる繰り返しモジュロ

存在する場合、B固定小数点乗算逆数 を計算することを検討する価値があるかもしれません。たとえば、コンパイル時の定数では、gccは128bより狭い型の最適化を行います。

_uint64_t modulo_by_constant64(uint64_t A) { return A % 0x12345678ABULL; }

    movabs  rdx, -2233785418547900415
    mov     rax, rdi
    mul     rdx
    mov     rax, rdx             # wasted instruction, could have kept using RDX.
    movabs  rdx, 78187493547
    shr     rax, 36            # division result
    imul    rax, rdx           # multiply and subtract to get the modulo
    sub     rdi, rax
    mov     rax, rdi
    ret
_

x86の_mul r64_命令は64b * 64b => 128b(rdx:rax)乗算を行い、128b * 128b => 256b乗算を構築するためのビルディングブロックとして使用して、同じアルゴリズムを実装できます。必要なのは完全な256bの結果の上位半分のみなので、乗算をいくつか節約できます。

最近のIntel CPUは非常に高いパフォーマンスmul:3cレイテンシ、1クロックあたり1つのスループットを備えています。ただし、必要なシフトと加算の正確な組み合わせは定数によって異なるため、実行時に乗法逆数を計算する一般的なケースは、JITコンパイルバージョンまたは静的コンパイルバージョンとして使用するたびにそれほど効率的ではありません(事前計算オーバーヘッドに加えて)。

損益分岐点となるIDK。 JITコンパイルの場合、一般的に使用されるB値の生成コードをキャッシュしない限り、再利用は最大で200を超えます。 「通常の」方法では、200回の再利用の範囲になる可能性がありますが、128ビット/ 64ビット除算のモジュラー乗法逆数を見つけることは、IDKにどのくらいの費用がかかるでしょう。

libdivide はこれを行うことができますが、32および64ビットタイプに対してのみです。それでも、それはおそらく良い出発点です。

4
Peter Cordes

私はいくつかの考えを共有したいと思います。

MSNが提案しているのと同じくらい簡単ではありません。

式では:

(((AH % B) * ((2^64 - B) % B)) + (AL % B)) % B

乗算と加算の両方がオーバーフローする可能性があります。それを考慮に入れて、一般的な概念をいくつかの変更を加えて使用することはできると思いますが、何かがそれを本当に恐ろしくするだろうと私に言います。

MSVCで64ビットのモジュロ演算がどのように実装されているか知りたくて、何かを見つけようとしました。私はアセンブリを本当に知りません、そしてVC\crt\src\intel\llrem.asmのソースなしで私が利用できたのはExpressエディションだけでした、しかし私は少し遊んだ後何が起こっているのかいくつかの考えを得ることができたと思いますデバッガーと逆アセンブリ出力を使用します。正の整数と除数> = 2 ^ 32の場合の剰余の計算方法を理解しようとしました。もちろん負の数を処理するコードはいくつかありますが、私はそれを掘り下げませんでした。

ここに私がそれを見る方法があります:

除数> = 2 ^ 32の場合、除数と除数の両方が、除数を32ビットに合わせるために必要なだけ右にシフトされます。言い換えると、除数を2進数で書くのにn桁かかり、n> 32の場合、除数と被除数の両方のn-32最下位桁が破棄されます。その後、64ビット整数を32ビット整数で除算するハードウェアサポートを使用して除算が実行されます。結果は正しくないかもしれませんが、結果が最大で1ずれる可能性があることは証明できると思います。除算後、除数(元の除数)に結果を掛け、その積を被除数から差し引きます。次に、必要に応じて(除算の結果が1でずれていた場合)、除数を加算または減算することで修正されます。

64ビットによる32ビット除算のハードウェアサポートを利用して、128ビット整数を32ビット1で除算するのは簡単です。除数が2 ^ 32未満の場合、次のように4除算を実行して剰余を計算できます。

配当が次の場所に保存されていると仮定します。

DWORD dividend[4] = ...

残りは入ります:

DWORD remainder;

1) Divide dividend[3] by divisor. Store the remainder in remainder.
2) Divide QWORD (remainder:dividend[2]) by divisor. Store the remainder in remainder.
3) Divide QWORD (remainder:dividend[1]) by divisor. Store the remainder in remainder.
4) Divide QWORD (remainder:dividend[0]) by divisor. Store the remainder in remainder.

これらの4つのステップの後、変数の残りはあなたが探しているものを保持します。 (エンディアネスが間違っていても私を殺さないでください。私もプログラマーではありません)

除数が2 ^ 32-1よりも大きい場合、良いニュースはありません。 MSVCが使用していると私が確信している前述の手順で、シフト後の結果が1以下であるという完全な証拠はありません。ただし、破棄される部分は約2 ^ 31倍の除数より少なく、被除数は2 ^ 64未満で、約数は2 ^ 32-1より大きいという事実と関係があると思います。なので、結果は2 ^ 32未満です。

被除数が128ビットの場合、ビットを破棄するトリックは機能しません。したがって、一般的には、最良の解決策はおそらくGJまたはcafによって提案されたものです。 (まあ、それはおそらくビットの破棄が機能したとしても最高でしょう。128ビット整数の除算、乗算減算、および修正は遅くなるかもしれません。)

浮動小数点ハードウェアの使用も考えていました。 x87浮動小数点ユニットは、64ビット長の80ビット精度フォーマットを使用します。 64ビット×64ビット除算の正確な結果が得られると思います。 (残りは直接ではなく、「MSVC手順」のように乗算と減算を使用した残りも)。被除数> = 2 ^ 64および<2 ^ 128の場合、浮動小数点形式で格納すると、「MSVC手順」で最下位ビットを破棄するのと似ているように見えます。たぶん誰かがその場合のエラーを証明し、それが役に立つと思うかもしれません。 GJのソリューションよりも高速になる可能性があるかどうかはわかりませんが、試してみる価値はあります。

4
Maciej Hehl

解決策は、正確に解決しようとしていることに依存します。

例えば。 64ビット整数を法とするリングで算術演算を行う場合、 Montgomerys reduction を使用すると非常に効率的です。もちろん、これは同じ係数を何度も使用し、リングの要素を特別な表現に変換することで利益が得られることを前提としています。


このMontgomerys削減の速度の非常に大まかな見積もりを与えるために:私は、2.4Ghzコア2で1600 nsで64ビットのモジュラスと指数でモジュラー指数を実行する古いベンチマークを持っています。この指数は、約96モジュラー乗算(およびモジュラー削減)、したがってモジュラー乗算あたり約40サイクルが必要です。

4
Accipitridae

原則として、除算は低速で、乗算は高速で、ビットシフトも高速です。これまでの回答で私が見てきたことから、ほとんどの回答はビットシフトを使用したブルートフォースアプローチを使用しています。別の方法があります。それがより速いかどうかはまだわかりません(別名プロファイル)。

除算する代わりに、逆数を掛けます。したがって、A%Bを検出するには、まずB ... 1/Bの逆数を計算します。これは、収束のニュートンラフソン法を使用するいくつかのループで実行できます。これを適切に行うには、テーブル内の初期値の適切なセットに依存します。

逆数に収束するニュートンラフソン法の詳細については、 http://en.wikipedia.org/wiki/Division_(digital) を参照してください。

逆数が得られると、商Q = A * 1/Bになります。

残りのR = A-Q * B。

これがブルートフォースよりも速いかどうかを判断するには(32ビットレジスターを使用して64ビットと128ビットの数値をシミュレートするため、さらに多くの乗算があるため、プロファイリングします。

コード内でBが定数の場合、逆数を事前に計算し、最後の2つの式を使用して単純に計算できます。これは、ビットシフトよりも速いと確信しています。

お役に立てれば。

2
Sparky

最近のx86マシンを使用している場合、SSE2 +用の128ビットレジスタがあります。基本的なx86以外のアセンブリを記述しようとしたことはありませんが、そこにいくつかのガイドがあると思います。

1
Adam Shiemke

128ビットの符号なしと63ビットの符号なしで十分な場合は、最大63サイクルのループで実行できます。

これを1ビットに制限することにより、MSNのオーバーフロー問題を提案されたソリューションと考えてください。これを行うには、問題を2つのモジュラー乗算に分割し、最後に結果を追加します。

次の例では、上位が64ビットの最上位に対応し、下位が64ビットの下位に対応し、divは除数です。

unsigned 128_mod(uint64_t upper, uint64_t lower, uint64_t div) {
  uint64_t result = 0;
  uint64_t a = (~0%div)+1;
  upper %= div; // the resulting bit-length determines number of cycles required

  // first we work out modular multiplication of (2^64*upper)%div
  while (upper != 0){
    if(upper&1 == 1){
      result += a;
      if(result >= div){result -= div;}
    }
    a <<= 1;
    if(a >= div){a -= div;}
    upper >>= 1;
  }

  // add up the 2 results and return the modulus
  if(lower>div){lower -= div;}
  return (lower+result)%div;
}

唯一の問題は、除数が64ビットの場合、1ビットのオーバーフロー(情報の損失)が発生し、誤った結果が生じることです。

オーバーフローを処理するためのきちんとした方法を私が理解していないことは私を困惑させます。

1
CookieNinja