128-битное деление, присущее Visual C++

Мне интересно, нет ли в Visual C++ встроенной функции 128-битного деления?

Существует встроенная функция умножения 64x64=128 битов, называемая _umul128(), которая хорошо соответствует инструкции ассемблера MUL x64.

Естественно, я предполагал, что будет также встроенное 128/64=64-битное деление (моделирование инструкции DIV), но, к моему изумлению, ни Visual C++, ни Intel C++, похоже, не имеют его, по крайней мере, его нет в intrin.h.

Кто-нибудь может это подтвердить? Я попробовал grep'ing для имен функций в исполняемых файлах компилятора, но не смог найти _umul128, поэтому, думаю, я посмотрел не в ту точку.

Обновление: по крайней мере, теперь я нашел шаблон "umul128" (без начального подчеркивания) в c1.dll Visual C++ 2010. Вокруг него перечислены все другие встроенные функции, но, к сожалению, нет "udiv128" или тому подобного:(кажется, они действительно "забыли" реализовать это.

Для пояснения: я ищу не только 128-битный тип данных, но и способ делить 128-битное скалярное int на 64-битное int в C++. Либо встроенная функция, либо собственная поддержка 128-битного целого числа решит мою проблему.

Редактировать: ответ - нет, в Visual Studio 2010 или 2012 нет встроенного _udiv128.

5 ответов

Решение

I am no expert, but I dug this up:

http://research.swtch.com/2008/01/division-via-multiplication.html

Интересные вещи. Надеюсь, поможет.

EDIT: This is insightful too: http://www.gamedev.net/topic/508197-x64-div-intrinsic/

Если вы не возражаете против небольших взломов, это может помочь (только в 64-битном режиме, не проверено):

#include <windows.h>
#include <stdio.h>

unsigned char udiv128Data[] =
{
  0x48, 0x89, 0xD0, // mov rax,rdx
  0x48, 0x89, 0xCA, // mov rdx,rcx
  0x49, 0xF7, 0xF0, // div r8
  0x49, 0x89, 0x11, // mov [r9],rdx
  0xC3              // ret
};

unsigned char sdiv128Data[] =
{
  0x48, 0x89, 0xD0, // mov rax,rdx
  0x48, 0x89, 0xCA, // mov rdx,rcx
  0x49, 0xF7, 0xF8, // idiv r8
  0x49, 0x89, 0x11, // mov [r9],rdx
  0xC3              // ret
};

unsigned __int64 (__fastcall *udiv128)(unsigned __int64 numhi,
                                       unsigned __int64 numlo,
                                       unsigned __int64 den,
                                       unsigned __int64* rem) =
  (unsigned __int64 (__fastcall *)(unsigned __int64,
                                   unsigned __int64,
                                   unsigned __int64,
                                   unsigned __int64*))udiv128Data;

__int64 (__fastcall *sdiv128)(__int64 numhi,
                              __int64 numlo,
                              __int64 den,
                              __int64* rem) =
  (__int64 (__fastcall *)(__int64,
                          __int64,
                          __int64,
                          __int64*))sdiv128Data;

int main(void)
{
  DWORD dummy;
  unsigned __int64 ur;
  __int64 sr;
  VirtualProtect(udiv128Data, sizeof(udiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
  VirtualProtect(sdiv128Data, sizeof(sdiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
  printf("0x00000123456789ABCDEF000000000000 / 0x0001000000000000 = 0x%llX\n",
         udiv128(0x00000123456789AB, 0xCDEF000000000000, 0x0001000000000000, &ur));
  printf("-6 / -2 = %lld\n",
         sdiv128(-1, -6, -2, &sr));
  return 0;
}

Небольшое улучшение - на одну инструкцию меньше

extern "C" digit64 udiv128(digit64 low, digit64 hi, digit64 divisor, digit64 *remainder);

; Arguments
; RCX       Low Digit
; RDX       High Digit
; R8        Divisor
; R9        *Remainder

; RAX       Quotient upon return

.code
udiv128 proc
    mov rax, rcx    ; Put the low digit in place (hi is already there)
    div r8      ; 128 bit divide rdx-rax/r8 = rdx remainder, rax quotient
    mov [r9], rdx   ; Save the reminder
    ret     ; Return the quotient
udiv128 endp
end

Это доступно сейчас. Вы можете использовать _div128 а также _udiv128

В _div128intrinsic делит 128-битное целое число на 64-битное целое число. Возвращаемое значение содержит частное, а внутренняя функция возвращает остаток через параметр указателя._div128 специфичен для Microsoft.

В прошлом году было сказано, что он будет доступен с "Dev16", но я не уверен, какая это версия. Я предполагаю, что это VS 16.0 AKA VS2019, но документация на MSDN показывает, что он идет дальше VS2015

Спасибо @alexey-frunze, он работал с небольшой настройкой для VS2017, проверено с теми же параметрами с VS2019:

      #include <iostream>
#include <string.h>
#include <math.h>
#include <immintrin.h>
#define no_init_all
#include <windows.h>

unsigned char udiv128Data[] =
{
    0x48, 0x89, 0xD0, // mov rax,rdx
    0x48, 0x89, 0xCA, // mov rdx,rcx
    0x49, 0xF7, 0xF0, // div r8
    0x49, 0x89, 0x11, // mov [r9],rdx
    0xC3              // ret
};

unsigned char sdiv128Data[] =
{
    0x48, 0x89, 0xD0, // mov rax,rdx
    0x48, 0x89, 0xCA, // mov rdx,rcx
    0x49, 0xF7, 0xF8, // idiv r8
    0x49, 0x89, 0x11, // mov [r9],rdx
    0xC3              // ret
};

unsigned __int64(__fastcall* udiv128)(
    unsigned __int64 numhi,
    unsigned __int64 numlo,
    unsigned __int64 den,
    unsigned __int64* rem) =
    (unsigned __int64(__fastcall*)(
        unsigned __int64,
        unsigned __int64,
        unsigned __int64,
        unsigned __int64*))
        ((unsigned __int64*)udiv128Data);

__int64(__fastcall *sdiv128)(
    __int64 numhi,
    __int64 numlo,
    __int64 den,
    __int64* rem) =
    (__int64(__fastcall *)(
        __int64,
        __int64,
        __int64,
        __int64*))
        ((__int64*)sdiv128Data);

void test1()
{
    unsigned __int64 a = 0x3c95ba9e6a637e7;
    unsigned __int64 b = 0x37e739d13a6d036;
    unsigned __int64 c = 0xa6d036507ecc7a7;
    unsigned __int64 d = 0x7ecc37a70c26e68;
    unsigned __int64 e = 0x6e68ac7e5f15726;

    DWORD dummy;
    VirtualProtect(udiv128Data, sizeof(udiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
    e = udiv128(a, b, c, &d);

    printf("d = %llx, e = %llx\n", d, e);    // d = 1ed37bdf861c50, e = 5cf9ffa49b0ec9aa

}

void test2()
{
    __int64 a = 0x3c95ba9e6a637e7;
    __int64 b = 0x37e739d13a6d036;
    __int64 c = 0xa6d036507ecc7a7;
    __int64 d = 0x7ecc37a70c26e68;
    __int64 e = 0x6e68ac7e5f15726;

    DWORD dummy;
    VirtualProtect(sdiv128Data, sizeof(sdiv128Data), PAGE_EXECUTE_READWRITE, &dummy);
    e = sdiv128(a, b, c, &d);

    printf("d = %llx, e = %llx\n", d, e);    // d = 1ed37bdf861c50, e = 5cf9ffa49b0ec9aa

}

int main()
{
    test1();
    test2();

    return 0;
}
Другие вопросы по тегам