Сборщик 64б отдела

Мне нужен простой способ разделить целые числа без знака 64b на ассемблере для x86. Мой номер хранится в двух 32-битных регистрах EDX:EAX, и мне нужно поместить результат обратно в EDX:EAX. Фактор находится в 32b целом числе. Какой-нибудь код, пожалуйста?


person Nick    schedule 18.10.2012    source источник
comment
Просто для уточнения, вы имеете в виду с использованием инструкций x64 или без них? То есть это просто вопрос получения данных в 64-битных регистрах (например, RAX), выполнения 64-битного деления, а затем разделения их обратно на 32-битные регистры, или вы пытаетесь эмулировать 64-битное деление на 32-битном процессоре. ?   -  person WeirdlyCheezy    schedule 19.10.2012
comment
Без 64б рег - пытаюсь эмулировать 64б деление на 32б.   -  person Nick    schedule 19.10.2012
comment
Если это так, похоже, вы в основном реализуете двоичное деление почти с нуля. Это кажется немного широким для вопроса SO, imo. что ты уже испробовал? Есть ли более конкретная часть, на которой вы застряли?   -  person WeirdlyCheezy    schedule 19.10.2012
comment
Мне нужна идея, как это сделать. Мне не нужны никакие проверки (цветовые потоки, деление на 0, управление знаком) или что-то в этом роде. Просто раздели и сохрани. Я думаю, это около 15 строк кода :)   -  person Nick    schedule 19.10.2012
comment
Я посмотрю, что я могу собрать завтра, если к тому времени никто не опубликовал решение.   -  person WeirdlyCheezy    schedule 19.10.2012
comment
Если вы действительно выполняете деление 64/64=64,64, вы можете расширить код из этого ответа. Это деление 16/8=8,8. Используя пары 32-разрядных целых чисел для представления 64-разрядных величин вместо 8-разрядных и зацикливая 64 раза вместо 8, вы можете преобразовать этот код в деление 128/64=64,64, точно так же, как 64-разрядное div в 64-битный режим.   -  person Alexey Frunze    schedule 19.10.2012
comment
@AlexeyFrunze: если делитель больше, чем поддерживает процессор, вы не можете разбить его на несколько делений. Это означает, что (для 32-битного 80x86) вы можете сделать 123456-битное, разделенное на 32-битное с несколькими делениями, но не можете сделать 64-битное, разделенное на 64-битное с несколькими делениями.   -  person Brendan    schedule 19.10.2012
comment
@ Брендан Я не имел в виду, что вы можете разбить большее деление на более мелкие (кстати, в некоторых случаях вы действительно можете это сделать, если позволяют значения дивиденда и делителя). Я просто предложил повторно использовать тот же базовый алгоритм длинного деления (реализованный в связанном ответе) для больших целых чисел.   -  person Alexey Frunze    schedule 19.10.2012
comment
@ Алексей Фрунзе Судя по моему чтению поста, операция на самом деле выполняет 64-битный числитель, 32-битный знаменатель, так что этот подход применим напрямую.   -  person WeirdlyCheezy    schedule 19.10.2012


Ответы (3)


Если я правильно интерпретирую ваш вопрос (особенно часть 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

А теперь эквивалентный код деления на ассемблере (синтаксис NASM) без проверки деления на 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
person Alexey Frunze    schedule 20.10.2012
comment
[РЕШЕНО] .. Вау! Работающий! Спасибо, это то, что мне нужно :) ... Спасибо вам всем! - person Nick; 21.10.2012
comment
Тьфу... пожалуйста, не используйте xchg с операндом памяти, если вам действительно не нужна блокировка шины. - person fuz; 05.10.2017


Краткий обзор терминологии: числитель/делитель = результат + остаток/делитель

Сначала проверьте, равен ли делитель нулю (прекратите, если это так).

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

Сдвиньте делитель влево, пока не будет установлен старший бит, отслеживая, сколько сдвигов вы сделали:

    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).

person Brendan    schedule 19.10.2012