Деление числа, представленного двумя словами, на число, представленное одним?

У меня есть два числа, X и Y.

Y является одним целым примитивом без знака, например long unsigned int, (В этом случае нет более крупного примитива для upcast до выполнения операции.)

X представлен двумя примитивами: X0 того же типа, что и Y и представляет младшие биты X, а X1 того же типа и представляет старшие биты X.

X / Y всегда будет представимым с использованием того же типа, что и Y, т.е. можно предположить, что операция не переполняется. (Поскольку X случайно является произведением двух значений того же типа, что и Y, одно из которых меньше или равно Y.)

Какой эффективный способ определить результат этого деления?

3 ответа

Вы не указали платформу, которая имеет решающее значение для ответа.

X / Y всегда будет представимым с использованием того же типа, что и Y, т.е. можно предположить, что операция не переполняется. (Поскольку X случайно является произведением двух значений того же типа, что и Y, одно из которых меньше или равно Y.)

В архитектуре x86-64 вы можете воспользоваться этим фактом, разделив RDX:RAX пара, так что это на самом деле так же, как если бы у вас был один "склеенный" 128-битный регистр для дивидендов. Однако помните, что если вышеупомянутый инвариант не всегда выполняется, вы получите исключение деления из CPU.

Тем не менее, одна реализация должна использовать встроенную сборку, например:

/* divides x1:x0 pair by y, assumes that quotient <= UINT64_MAX  */
uint64_t udiv128_64_unsafe(uint64_t x0, uint64_t x1, uint64_t y)
{
    __asm__ (
        "divq\t%3"
        : "=a" (x0)
        : "0" (x0), "d" (x1), "rm" (y)
    );
    return x0;
}

который GCC 6.3.0 переводит хорошо (в -O1):

udiv128_64_unsafe:
        mov     rcx, rdx            ; place the y (divisor) in RCX 
        mov     rax, rdi            ; low part of the dividend (x0)
        mov     rdx, rsi            ; high part of the divided (x1)
        divq    rcx                 ; RAX = RDX:RAX / RCX
        ret                         ; RAX is return value

Например, для X = 65454567423355465643444545, Y = 86439334393432232:

#include <stdio.h>
#include <inttypes.h>

uint64_t udiv128_64_unsafe(uint64_t x0, uint64_t x1, uint64_t y) { ... }

int main(void)
{
    printf("%" PRIu64 "\n", udiv128_64_unsafe(0x35c0ecb3fea1c941ULL, 0x36248bULL,
        86439334393432232ULL));
    return 0;
}

данная программа тестового драйвера дает:

757231275

gcc имеет __int128 а также unsigned __int128 для архитектуры x86. В прошлом я успешно использовал его для выполнения описанных вами операций. Я уверен, что все основные компиляторы имеют эквиваленты.

"Делить двузначное число на 1 цифру, давая 1-значное частное и остаток" - это основной примитив, который вам нужен для синтеза больших делений. Если вы не имеете его (с цифрой == unsigned long int) в вашем оборудовании, вам нужно использовать меньшие цифры.

В вашем случае, разделите Y на 2 половинных целых числа и X на 4 половинных целых числа и делите таким образом.

Другие вопросы по тегам