2012-10-18 4 views
3

x86 용 어셈블러에서 부호없는 64 비트 정수를 쉽게 나눌 수있는 방법이 필요합니다. 내 번호는 두 개의 32b 레지스터에 저장됩니다. EDX : EAX 및 결과를 EDX : EAX에 다시 저장해야합니다. Factor는 32b 정수입니다. 일부 코드는 제발?어셈블러 64b division

+0

설명을 위해 x64 명령어 사용 여부에 관계없이 사용 하시겠습니까? 즉, 데이터를 64 비트 레지스터 (예 : RAX)로 가져 와서 64 비트 나누기를 한 다음 32 비트 레지스터로 다시 분할하거나 32 비트 프로세서에서 64 비트 나누기를 에뮬레이트하려고하는지 여부입니다 ? – WeirdlyCheezy

+0

64b reg없이 - 32b에서 64b division을 에뮬레이트하려고합니다. – Nick

+0

그런 경우라면 거의 기본적으로 바이너리 분할을 구현하는 것처럼 들립니다. 그것은 SO 질문에 대해 조금 광범위하게 보인다. 지금까지 뭐 해봤 어? 더 구체적인 부분이 있습니까? – WeirdlyCheezy

답변

5

정확하게 질문을 해석하면 (특히 부품 번호 Factor is in 32b integer) 64 비트 배당을 32 비트 제수로 나누고 64 비트 지수를 얻고 싶습니다.

그 해석이 정확하다면 실제로 32 비트 코드로하기 쉽습니다.

아이디어는 제수에 의해 배당의 "절반"을 나누고 나머지를 두 번째 나누기의 첫 번째 나누기에서 다시 사용한다는 아이디어입니다.

C 코드 도시 그 방법 :

#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; 
} 

출력 (ideone)

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

이제 0으로 나누기 확인없이 어셈블리 (NASM 구문)에 해당하는 분할 코드

:

; 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 
+0

[해독] .. 와우! 일! 고마워요.이게 내가 필요한 것입니다. :) ... 다들 고마워! – Nick

+1

... 정말로 버스 잠금을 원하지 않는 한, 메모리 피연산자와 함께'xchg'를 사용하지 마십시오. – fuz

0

div 명령어 사용은 어떻게됩니까?

+0

물론 두 개의 레지스터에 하나의 긴 번호가 저장된 경우 한두 개의 "div"만 사용할 수는 없습니다. 몇 가지 방법, 숫자 (EDX)의 한 부분, 숫자의 두 번째 부분 (EAX)을 나누는 방법, 결과를 결합하고 최종 결과를 EDX : EAX에 다시 저장하는 방법이 필요합니다. 나는이 방법을 찾고있다. 어떻게하는지. – Nick

+0

당신의 제수도 64 비트입니까? –

0

빠른 용어 요점 : 분자/제수 = + 결과 나머지/제수

먼저 확인 약수 (이 경우 중단) 0 인 경우. 제로로

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 

는 지금을 이동하면 한 얼마나 많은 변화를 추적하면서 MSB가 설정 될 때까지

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

이동은 제수 왼쪽 원래 위치로 약수. 이때 EDX에서

.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 

: 결과에 약간의 분자 잔존하고 설정에서 현재 제수를 감산하는 동안 언제든지 현재 제수 전류 분자 미만 EAX는 그것이 동일한 값이고, EDI는 : ESI의 결과이며 ECX : EBX가 나머지입니다.

경고 : 위의 모든 내용은 완전히 테스트되지 않았습니다. 그것은 단지 예시/묘사 일뿐입니다.

참고 : 숫자가 서명 된 경우 먼저 분자 및 제수에서 부호 비트를 제거해야합니다. 결과에 부호 비트를 설정하고 나머지는 나중에 설정하십시오 (sign = numerator_sign XOR divisor_sign).