4

x86 のアセンブラーで 64b 符号なし整数を分割する簡単な方法が必要です。私の番号は 2 つの 32b レジスタ EDX:EAX に保存され、結果を EDX:EAX に戻す必要があります。係数は 32b 整数です。いくつかのコードをお願いします。

4

3 に答える 3

5

If I interpret your question correctly (particularly the part Factor is in 32b integer), you want to divide a 64-bit dividend by a 32-bit divisor and get a 64-bit quotient.

If that interpretation is correct, then it's actually easy to do in 32-bit code.

The idea is that you divide both "halves" of the dividend by the divisor and reuse the remainder from the first division for the second division.

C code illustrating how to do it:

#include <stdio.h>
#include <limits.h>

#define C_ASSERT(expr) extern char CAssertExtern[(expr)?1:-1]

#if UINT_MAX >= 0xFFFFFFFF
typedef unsigned int uint32;
#else
typedef unsigned long uint32;
#endif
typedef unsigned long long uint64;

typedef unsigned long ulong;

// Make sure uint32=32 bits and uint64=64 bits
C_ASSERT(sizeof(uint32) * CHAR_BIT == 32);
C_ASSERT(sizeof(uint64) * CHAR_BIT == 64);

int div64by32eq64(uint64* dividend, uint32 divisor)
{
  uint32 dividendHi = (uint32)(*dividend >> 32);
  uint32 dividendLo = (uint32)*dividend;
  uint32 quotientHi;
  uint32 quotientLo;

  if (divisor == 0)
    return 0;

  // This can be done as one 32-bit DIV, e.g. "div ecx"
  quotientHi = dividendHi / divisor;
  dividendHi = dividendHi % divisor;

  // This can be done as another 32-bit DIV, e.g. "div ecx"
  quotientLo = (uint32)((((uint64)dividendHi << 32) + dividendLo) / divisor);

  *dividend = ((uint64)quotientHi << 32) + quotientLo;

  return 1;
}

int main(void)
{
  static const struct
  {
    uint64 dividend;
    uint32 divisor;
  } testData[] =
  {
    { 1 , 0 },
    { 0xFFFFFFFFFFFFFFFFULL, 1 },
    { 0xFFFFFFFFFFFFFFFFULL, 2 },
    { 0xFFFFFFFF00000000ULL, 0xFFFFFFFFUL },
    { 0xFFFFFFFFFFFFFFFFULL, 0xFFFFFFFFUL },
  };
  int i;

  for (i = 0; i < sizeof(testData)/sizeof(testData[0]); i++)
  {
    uint64 dividend = testData[i].dividend;
    uint32 divisor = testData[i].divisor;

    printf("0x%016llX / 0x%08lX = ", dividend, (ulong)divisor);

    if (div64by32eq64(&dividend, divisor))
      printf("0x%016llX\n", dividend);
    else
      printf("division by 0 error\n");
  }

  return 0;
}

Output (ideone):

0x0000000000000001 / 0x00000000 = division by 0 error
0xFFFFFFFFFFFFFFFF / 0x00000001 = 0xFFFFFFFFFFFFFFFF
0xFFFFFFFFFFFFFFFF / 0x00000002 = 0x7FFFFFFFFFFFFFFF
0xFFFFFFFF00000000 / 0xFFFFFFFF = 0x0000000100000000
0xFFFFFFFFFFFFFFFF / 0xFFFFFFFF = 0x0000000100000001

And now the equivalent division code in assembly (NASM syntax) without checking for division by 0:

; 64-bit dividend
mov edx, 0xFFFFFFFF
mov eax, 0xFFFFFFFF

; 32-bit divisor
mov ecx, 0xFFFFFFFF

push eax
mov eax, edx
xor edx, edx
div ecx ; get high 32 bits of quotient
xchg eax, [esp] ; store them on stack, get low 32 bits of dividend
div ecx ; get low 32 bits of quotient
pop edx ; 64-bit quotient in edx:eax now
; edx:eax should now be equal 0x0000000100000001
于 2012-10-20T10:17:12.710 に答える
0

簡単な用語の要約: 分子/除数 = 結果 + 剰余/除数

最初に、除数がゼロかどうかを確認します (ゼロの場合は中止します)。

     test eax,eax
     jne .ok
     test edx,edx
     je .divisionByZero
.ok:

実行したシフト数を追跡​​しながら、MSB が設定されるまで除数を左にシフトします。

    xor ebp,ebp            ;ebp = bits shifted so far
    test edx,(1 << 31)
    jne .l2
.l1:
    shld edx,eax,1
    shl eax,1
    inc ebp
    test edx,(1 << 31)
    jne .l1
.l2:

現在の結果をゼロに設定します。

    xor esi,esi
    xor edi,edi

ここで除数を元の位置に戻します。残りの分子から現在の除数を減算し、現在の除数が現在の分子より小さい場合は常に結果にビットを設定します。

.nextBit:
    shld edi,esi,1
    shl esi,1

    cmp ecx,edx
    jb .doneBit
    ja .subtract
    cmp ebx,eax
    jb .doneBit

.subtract:
    sub ecx,edx
    sbb ebx,eax
    or esi,1

.doneBit:
    sub ebp,1
    jnc .nextBit

この時点で、EDX:EAX は以前と同じ値であり、EDI:ESI は結果であり、ECX:EBX は残りです。

警告: 上記はすべて完全にテストされていません。これは単なる例/説明です。

注: 数値が符号付きの場合は、最初に分子と除数から符号ビットを削除する必要があります。次に、符号ビットを結果と剰余に設定します ( sign = numerator_sign XOR divisor_sign)。

于 2012-10-19T02:26:36.987 に答える
0

div命令を使用するとどうなりますか?

于 2012-10-18T23:25:04.583 に答える