Mail.ruПочтаМой МирОдноклассникиВКонтактеИгрыЗнакомстваНовостиКалендарьОблакоЗаметкиВсе проекты

Умножение двух 64 битных чисел и получение старших 64 бит

Илья Горбачев Знаток (339), открыт 5 дней назад
Необходимо умножить два 64 битных числа и сохранить старшие 64 бита полученного 128 битного результата в какую-либо переменную.

Моя реализация на Си:

 #include <stdio.h>

int main()
{
unsigned long long a,b,c;
c = a * b >> 64;
printf("Число: %i", c);

return 0;
}
2 ответа
Святослав Ясновидец Мастер (2008) 5 дней назад
#include <stdio.h>

int main()
{
unsigned long long a = 0xFFFFFFFFFFFFFFFF; // Пример значения для a
unsigned long long b = 0xFFFFFFFFFFFFFFFF; // Пример значения для b
unsigned __int128 result = (unsigned __int128)a * (unsigned __int128)b;
unsigned long long high_bits = (unsigned long long)(result >> 64);

printf("Старшие 64 бита: %llu\n", high_bits);

return 0;
}
Святослав Ясновидец Мастер (2008) 5 дней назад
Пример вывода:
Copy
Старшие 64 бита: 18446744073709551615
Илья Горбачев Знаток (339) Святослав Ясновидец , Однако также в программе понадобиться включить библиотеку stdlib.h для корректной работы
Папа Высший разум (145583) 5 дней назад
Молодец. Ты сделал программу, превращающую случайный мусор из стека в тождественный ноль (т.к. 64-битная нижняя часть произведения сдвигается вправо на 64 бита). Но для этого совершенно не нужно умножение, просто печатай 0, и всё.

Если всё же нужно умножение, то:

1) Только для GCC/Clang: преобразуй один из операндов в unsigned __int128 и умножай, примерно как предложила нейросеть выше, только попроще.
 uint64_t a, b;
cin >> a >> b;
cout << uint64_t((unsigned __int128)a * b >> 64) << endl;
Лучше преобразовывать только один операнд, тогда если попадётся тупой компилятор, он всё равно сделает только 2 умножения, а не 3 и не 4, требуемых для полностью 128-битного умножения. Умный компилятор отследит, что числа - изначально 64-битные и обойдётся одним умножением.

2) Только для MSVC: используй intrinsic _umul128, принимающий два 64-битных множителя и указатель на 64-битную переменную для записи старших бит произведения.
 uint64_t a, b, p;
cin >> a >> b;
_umul128(a, b, &p); // младшие биты выбрасываем
cout << p << endl;

3) Полностью универсальное решение - побить оба операнда на 32-битные половинки и сделать 4 умножения (или 3 с оптимизацией Карацубы - см. Википедию) и соотв. количество сдвигов и сложений. Но это, конечно, дольше, чем задействовать встроенные механизмы, т.к. они переводятся компилятором в умножение двойной точности, поддерживаемое практически любым современным процом, а тут нам самим придётся выполнять закат Солнца вручную.
 uint64_t a, b;
cin >> a >> b;
uint64_t ah = a >> 32, al = a & UINT_MAX, bh = b >> 32, bl = b & UINT_MAX;
uint64_t ph = ah * bh, pl = al * bl,
pm = (ah + al) * (bh + bl) - ph - pl
p = ph + (pm >> 32) + ((pl >> 32) + (pm & UINT_MAX) >> 32);
cout << p << endl;
Здесь умножаем раздельно две старших и две младших половинки, Карацубой за 1 умножение вычисляем крест-накрест ah * bl + al * bh, а затем его старшие 32 бита суммируем с произведением старших половинок, а от младших вычисляем перенос, если есть.
Илья ГорбачевЗнаток (339) 4 дня назад
Спасибо за ответ, не представляю как это реализовывалось в коде, потому что изначально был дан исходный код на си, где шифровалась строка способом исключающего или, а затем нужно было язык ассемблера превратить обратно в си код. Там были примерно следующие несколько строк:

 mov rdx, 0xcccccccccccccccd # константа для "перемешивания" битов
mov eax, 0x0
mov al, [rbp-...] # байт строки
mul rdx
mov rcx, rdx
...
То-есть, здесь мы умножаем данную константу на байт строки, тем самым выполняя первый этап шифрования, а затем без лишних операций можно просто взять значение из регистра rdx, где как раз и хранится нужное значение в виде 64 битного значения. Далее его можно перевести обратно в одно байтное и т.д., и я просто не мог понять, как исходник мог скомпилироваться в это.
Похожие вопросы