Главная страница
Top.Mail.Ru    Яндекс.Метрика
Текущий архив: 2017.01.15;
Скачать: CL | DM;

Вниз

Умножение и сложение UInt64 с переполнением.   Найти похожие ветки 

 
DayGaykin ©   (2015-11-19 14:20) [0]

Delphi XE5. Проект 64 бита.

Есть большое число представленное в виде массива A: array[0..3] of UInt64.
Есть число M: UInt64;
Необходимо умножить A на M (и положить в A).
Подскажите как?

Как я понимаю это умножение в системе счисления 2^64.
Умножаем A[3] на M, результат кладем в A[3], а все что выше (в школе это называлось "в уме") прибавляем к A[2]. Если переполнилось, то прибавляем к A[1] и так далее.

Весь вопрос в том, как получить это значение "в уме", которое нужно прибавить к следующему разряду?

Причем как при сложении, так и при умножении.


 
brother ©   (2015-11-19 14:27) [1]

Даже не считая: как учитывать переполнение после умножения?


 
DayGaykin ©   (2015-11-19 14:29) [2]

Немного исследовав вопрос я нашел что при умножении в 32х битах командой mul результат кладется в EAX, а то что "в уме" в EDX.

Вопрос, справедливо ли это и для 64 бит и самое главное, как это использовать в delphi?


 
DayGaykin ©   (2015-11-19 15:20) [3]

Оказывается на delphi под 64 можно писать функции на асме:
Написал такую функцию:

function Mul(A: UInt64;  M: UInt64; out X: UInt64): UInt64; assembler;
asm
 mov rax, rcx
 mul rdx
 mov [r8], rdx
end;


Возвращает A*M, а то что осталось "в уме" в X.

Правильно ли?


 
Rouse_ ©   (2015-11-19 17:10) [4]

Самое простое, (конечно не самое быстрое) это побитовое сложение, которое можно нарастить хоть до 1024-битных чисел.
Но так то вообще-то да, RDX - это переполнение.

mul rdx ; Multiply rax by rdx
; rax=low bits, rdx overflow


 
DayGaykin ©   (2015-11-19 17:47) [5]

Спасибо.
Сань, глянь, пожалуйста еще:

var A: array[0..3] of UInt64;
...
function Mul(AN: PUInt64;  M: UInt64; A0: PUInt64): boolean; assembler;
asm
 // rcx - указатель на текущий разряд. Вначале - последний слева разряд.
 // r8 - указатель на первый слева разряд.

 mov r9, rdx    // Множитель
 xor r10, r10   // То что на уме
 xor rbx, rbx   // Для определения переполнения

@start:
 mov rax, [rcx]
 mul r9         // Умножаем текущий разряд на множитель. rax - результат, rdx - на ум.
 add rax, r10   // Прибавляем то что раннее на ум шло.
 mov [rcx], rax // Сохраняем результат.
 seto bl        // А если при сложении было переполнение?
 add rdx, rbx   // Добавялем к тому что на ум пойдет.
 mov r10, rdx   // Записываем то что пошло на ум.

 sub rcx, 8
 cmp rcx, r8    // Переходим к следующему разряду.
 jnz @start

 // Возвращаем True - если результат не влез в число.
 cmp r10, 0
 setnz al
end;

Умножаем там:
Mul(@A[High(A), M, @A[0]]) // M - некий множитель

Вопрос: с точки зрения асма правильно? или надо какие-то регистры в стеке сохранять и восстанавливать?
Если не лень, алгоритм тоже посмотри.


 
DayGaykin ©   (2015-11-19 17:48) [6]

Mul(@A[High(A)], M, @A[0]) // M - некий множитель
Опечатался - тороплюсь!:)


 
DayGaykin ©   (2015-11-19 18:16) [7]

Если можно как-то уменьшить тело цикла - буду очень благодарен!
Даже удаление 1-2 команд сократит мои расчеты на несколько часов.


 
Rouse_ ©   (2015-11-19 18:31) [8]

На вскидку вроде правильно, но что-то мне не нравится. Не могу понять за что глаз зацепился.


 
Rouse_ ©   (2015-11-19 18:33) [9]

Удалено модератором
Примечание: пардон, вру...


 
Rouse_ ©   (2015-11-19 18:38) [10]

Нет, похоже не вру.
R8 регистр не контролируется, на входе. Если он не кратен 8 - то вечный цикл.


 
DayGaykin ©   (2015-11-19 18:50) [11]


> Rouse_ ©   (19.11.15 18:38) [10]
> Нет, похоже не вру.
> R8 регистр не контролируется, на входе. Если он не кратен
> 8 - то вечный цикл.

Это не может случиться:
Mul(@A[High(A)], M, @A[0]) -- Разница между @A[High(A)] и @A[0] всегда кратна 8, т.к. элементы UInt64.

Дело в том, что в примере я указал массив A: array[0..3] of UInt64;, а на деле в массиве 6 млн элементов. Такое больше число я ожидаю на выходе. Перемножить мне нужно порядка 16 млн чисел (UInt64). Поэтому мне нужно экономить на каждом такте. Переписывание функции на ASM позволило увеличить скорость почти в 4 раза.

Спасибо за проверку! Ты очень помог!


 
Rouse_ ©   (2015-11-19 18:57) [12]


> DayGaykin ©   (19.11.15 18:50) [11]
> Это не может случиться:

При таком вызове конечно не может, но ты сам попросил посмотреть :)
А ускорить, можно, но существенного прироста в данном частном случае не даст.


 
DayGaykin ©   (2015-11-19 19:00) [13]


> А ускорить, можно, но существенного прироста в данном частном
> случае не даст.

Тогда оставлю как есть. В последствии мне нужно будет доказать, что этот алгоритм выполняет именно умножение. Чем проще - тем лучше.


 
Rouse_ ©   (2015-11-19 19:42) [14]

О как, ты бы лучше тогда Sha подождал, он явно попридирчивей меня будет к коду


 
Rouse_ ©   (2015-11-19 19:49) [15]

Зы, rbx не обязательно, но желательно на выходе иметь таким же что и на входе (push/pop ему сделай в эпилоге и прологе)


 
Pavia ©   (2015-11-19 20:25) [16]


> Дело в том, что в примере я указал массив A: array[0..3]
> of UInt64;, а на деле в массиве 6 млн элементов. Такое больше
> число я ожидаю на выходе. Перемножить мне нужно порядка
> 16 млн чисел (UInt64).

Не от туда вы решаете задачу. Преждевременная оптимизация это ЗЛО.
Какую задачу вы решаете? Откуда взялись длинные числа? Почему используете школьный алгоритм умножения? А не например алгоритм Карацубы?


> А ускорить, можно, но существенного прироста в данном частном
> случае не даст.

Даст. Умножение легко паралелится. А векторность процессора в данном алгоритме не учтена ни каким образом.


 
Rouse_ ©   (2015-11-19 22:08) [17]


> Pavia ©   (19.11.15 20:25) [16]
> Даст. Умножение легко паралелится. А векторность процессора
> в данном алгоритме не учтена ни каким образом.

Я знаю, но в именно данном случае сильного прироста не даст


 
Sha ©   (2015-11-20 00:52) [18]

> DayGaykin ©   (19.11.15 17:47) [5]

Что сразу бросилось в глаза

1. Можно обойтись меньшим числом регистров, если переполнения обрабатывать командами adc или даже sbb.

2. Если развернуть цикл в 2 или в 4 раза, можно сэкономить на проверках и изменениях индекса.

P.S. Умножение немного странное - переполнение точно требуется выполнять от старших индексов к младшим?

P.P.S. Как-то привычнее передавать адрес первого и количество.


 
DayGaykin ©   (2015-11-20 03:14) [19]


> Sha ©   (20.11.15 00:52) [18]
>

Спасибо за ответ.
Развернул цикл, заменил манипуляции с rbx на adc. Скорость чуть выросла, а расчеты сократились на несколько часов с примерно 38 часов, до 31, что уже хорошо и приемлемо. Спасибо еще раз!


> Pavia ©   (19.11.15 20:25) [16]

Я буду дольше разбираться с алгоритмом, чем он будет считать. Нынешний позволит найти произведение всех чисел примерно за 31 час - что приемлемо.
К тому же мне нужен точный и понятный алгоритм, т.к. в последствии нужно будет доказать, что полученный результат является произведением заданных чисел. Доказательством будет представленный исходный код. Поэтому мне нужно соблюсти баланс между скоростью и понятностью.

Если хотите, могу выложить массив с множителями.


 
DayGaykin ©   (2015-11-20 03:26) [20]

Интересно! Насколько я знаю, когда я делаю mov rax, [rcx] или mov [rcx], rax или прыжки операционная система проверяет, есть ли у меня доступ к этой памяти. Можно ли как-то эту проверку отключить?


 
Pavia ©   (2015-11-20 06:45) [21]


> Интересно! Насколько я знаю, когда я делаю mov rax, [rcx]
> или mov [rcx], rax или прыжки операционная система проверяет,
>  есть ли у меня доступ к этой памяти. Можно ли как-то эту
> проверку отключить?

Не беспокойтесь такая проверка происходит за 1 такт. И не отключается.


> Я буду дольше разбираться с алгоритмом, чем он будет считать.
>  Нынешний позволит найти произведение всех чисел примерно
> за 31 час - что приемлемо.
 Дело ваша за секунды-минуты считать или за часы. Да и разбираться с ним проще. И доказывать проще так как алгоритм известный и для него всё доказано.

Хотя честно мне интересно было узнать для чего понадобилось делать умножение? Похоже это учебная задача, на освоение алгоритмов и я думаю вас заставят делать через Карацубу. Единственная задача которую я знаю где нужны длинные числа это шифрование. Но там уже всё реализовано по 100 раз. Поэтому проще взять готовое.
В астрономии и гис может не хватать точности 64 бит и там да выгодно использовать  128 бит. В остальных случаях случаях длинные числа бесполезны.


 
Sha ©   (2015-11-20 09:34) [22]

> DayGaykin

Код покажи, что в итоге получилось.
Может, там еще где-нибудь на спичках экономия возможна.


 
DayGaykin ©   (2015-11-20 12:20) [23]


> Код покажи, что в итоге получилось.
> Может, там еще где-нибудь на спичках экономия возможна.



function Mul(M{RCX}: UInt64): boolean; assembler;
asm
 mov r9, rcx    // Множитель
 xor r10, r10   // То что на уме

 mov r8, offset MyNumber[0] // Ссылка на первый элемент числа
 mov rcx, Digits64 * 8      // Длина
 lea rcx, rcx + r8 - 8      // rcx - последний разряд

@@1:
 mov rax, [rcx]
 mul r9         // Умножаем текущий разряд на множитель. rax - результат, rdx - на ум.
 add rax, r10   // Прибавляем то что раннее на ум шло.
 mov [rcx], rax // Сохраняем результат.
 adc rdx,0      // Если при прибавлении мы получили переполнение, прибавляем к следующему "на уме" единицу
 mov r10, rdx   // И помещаем для следующей итерации
 sub rcx, 8

 mov rax, [rcx]
 mul r9         // Умножаем текущий разряд на множитель. rax - результат, rdx - на ум.
 add rax, r10   // Прибавляем то что раннее на ум шло.
 mov [rcx], rax // Сохраняем результат.
 adc rdx,0      // Если при прибавлении мы получили переполнение, прибавляем к следующему "на уме" единицу
 mov r10, rdx   // И помещаем для следующей итерации
 sub rcx, 8

 cmp rcx, r8
 jnb @@1

 // Возвращаем True - если результат не влез в число.
 cmp r10, 0
 setnz al
end;

Немного переделаю еще так, чтобы не нужно было умножать нули вначале расчетов и чтобы функция не "портила" число, если оно не влезет.


> Pavia ©   (20.11.15 06:45) [21]

Одноразовый математический расчет. Ищут математики первый миллиард цифр числа Pi, вот и я кое чего ищу.


 
DayGaykin ©   (2015-11-20 19:18) [24]

Еще вопрос:
Вот получил я число (на самом деле еще считается), записанное в массиве:
N: array[0..6000000] of UInt64 (В памяти оно лежит так-же, как оно было бы записано в мысленном типе данных UInt384000064).
Насколько сложно получить логарифм этого числа (в принципе по любому основанию, для простоты можно взять основание 2^64)?
Целую часть логарифма получить, думаю, вообще не составляет труда, а как получить дробную?


 
Sha ©   (2015-11-20 21:16) [25]

> DayGaykin

Приближенно вычисляешь по любому удобному основанию, например, 2. Потом приводишь к нужному.

Непонятно мне, откуда у тебя часы получаются.
На мой взгляд, время должно быть совсем смешным для любого массива, который поместится в память.


 
DayGaykin ©   (2015-11-20 22:36) [26]


> Непонятно мне, откуда у тебя часы получаются.
> На мой взгляд, время должно быть совсем смешным для любого
> массива, который поместится в память.

6000000 итераций для каждого умножения. 16000000 умножений. Приличное количество итераций выходит.


 
DayGaykin ©   (2015-11-20 22:37) [27]

145 умножений в секунду.


 
Sha ©   (2015-11-21 00:39) [28]

А данные откуда? Столько в памяти не поместится.


 
DayGaykin ©   (2015-11-21 02:49) [29]


> А данные откуда? Столько в памяти не поместится.

Напишу позже, сейчас хочу оставить задачу в тайне, чтобы избежать ненужного флуда в ветке.
16млн умножений - это столько у меня заготовлено множителей. Конечно, не влезет в 6 млн разрядов в системе счисления 2^64. Задача позволяет мне не умножать все, а умножать только до тех пор пока старший разряд из этих 6 млн не примет какое-либо ненулевое значение.
Я думаю уже можно догадаться что за задача, но все-же прошу воздержаться от догадок.

P.S.
Пока у меня ведутся расчеты возник вопрос:
Есть у нас массив A[0..N-1] of UInt64, где N достаточно больше число (порядка 10^6--10^8).
Массив A содержит число, где в каждом элементе находится значение разряда в 2^64 системе счисления. Порядок большого значения не имеет.
Есть B: UInt64. - т.е. одноразрядное число.
Задача: умножить A на B и результат положить в A.
Неужели это умножение можно произвести быстрее чем в столбик?

При умножении, каждый разряд числа A в любом случае нужно будет изменить, каким бы не был алгоритм. В столбик на каждый разряд приходится всего одно умножение и два  сложения. Я считаю, что быстрее чем в столбик не получится.


 
Sha ©   (2015-11-21 09:26) [30]

Встречаются задачи, которые могут быть решены без умножения.
Просто мысль )


 
DayGaykin ©   (2015-11-21 14:58) [31]

Рассчитал число, теперь приступаю к его анализу и сразу возникла проблема/вопрос:

Может есть готовая библиотека для расчетов с плавающей точкой повышенной точности?

Мне нужно умножать, делить (или находить 1/x), находить логарифм. Желательно чтобы точность регулировалась, но хотелось бы не менее 1 млн значащих цифр.


 
Pavia ©   (2015-11-21 18:31) [32]


> При умножении, каждый разряд числа A в любом случае нужно
> будет изменить, каким бы не был алгоритм. В столбик на каждый
> разряд приходится всего одно умножение и два  сложения.
> Я считаю, что быстрее чем в столбик не получится.

Можно. Начните распаралеливать, и вы увидите что второе сложение можно "сократить".


> Может есть готовая библиотека для расчетов с плавающей точкой
> повышенной точности?

Только на Си, на паскале нету. Хотел делать но понял что это безперспективно достаточно чисел с плавающей точкой 64 бита. А если кому не хватает, то удвоить точность.


 
Sha ©   (2015-11-21 19:54) [33]

> Pavia ©   (21.11.15 18:31) [32]
> второе сложение можно "сократить"

Собственно операция сложения выполняется гораздо быстрее операции сложения с учетом переноса. А от переноса никуда не уйти. К тому же сильно "растащить" сложения с переносом не особо получится: мешают другие арифметические операции, а неарифметических там всего две. Добавить пару переименований, чтобы уйти от сложения - сомнительная идея.

У меня (на 32 битах) основной цикл столбика выглядит так:  

@3:
 mul edi
 xor ebx, ebx
 add ebp, eax
 mov eax, [esi]
 mov [esi-4], ebp
 adc ebx, edx

@0:
 mul edi
 xor ebp, ebp
 add ebx, eax
 mov eax, [esi+4]
 mov [esi], ebx
 adc ebp, edx

@1:
 mul edi
 xor ebx, ebx
 add ebp, eax
 mov eax, [esi+8]
 mov [esi+4], ebp
 adc ebx, edx

@2:
 mul edi
 xor ebp, ebp
 add ebx, eax
 mov eax, [esi+12]
 mov [esi+8], ebx
 adc ebp, edx

 add esi, 16
 sub ecx, 4
 jg @3


Мне не совсем видно, как здесь можно было бы повысить скорость.

С другой стороны, просто запустив несколько потоков вычислений по одному для каждой группы множителей, можно кратно ее увеличить.

С третьей стороны ) на 64 битах доступных регистров больше. Там можно в рамках одного потока на каждом ядре организовать 2 виртуальных потока вычислений, по одному для каждой из двух половинок большого числа, а потом "срастить" их. Возможно, это будет немного быстрее.


 
DayGaykin ©   (2015-11-21 20:18) [34]


> Там можно в рамках одного потока на каждом ядре организовать
> 2 виртуальных потока вычислений

Это как так?


 
Sha ©   (2015-11-21 20:35) [35]

> DayGaykin ©   (21.11.15 20:18) [34]

Грубо говоря идти по циклу не так: 0, 1, 2, ... N-2, N-1
А так: 0, N/2, 1, N/2+1, ... N/2-1, N-1


 
DayGaykin ©   (2015-11-21 22:49) [36]

Всем спасибо за помощь.

Число рассчитал, но моя идея по укорачиванию его записи провалилась, способ, который меня казался интересным, теперь мне кажется не эффективным.
Идея была перемножить первые N (как оказалось примерно 22 миллиона) простых чисел, а затем представить в виде небольшого количества слагаемых вида a^b. Поиск a и b я думал сделать следующим образом: a перебираем подряд, b рассчитываем с помощью логарифмирования. Перебирая "a" выбираем такие значение, для которых ln(M)/ln(a) максимально близко к натуральному. Другими словами дробная часть максимально близка к 0 или к 1.

Число у меня записано в бинарном виде и занимает чуть более 48 млн байт. Меня удивил тот факт, что этот файл не поддается сжатию ни 7z ни Zip. Этот факт меня еще больше убедил в сложности решаемой мной задачи. Можно, конечно, разбираться дальше, но мой любительский интерес на этом закончен.

Большое спасибо Sha и Rouse_ за комментарии и помощь.


 
DayGaykin ©   (2015-11-21 22:50) [37]

В предыдущем значении M - то самое мое число.



Страницы: 1 вся ветка

Текущий архив: 2017.01.15;
Скачать: CL | DM;

Наверх




Память: 0.58 MB
Время: 0.021 c
2-1421583719
Боб
2015-01-18 15:21
2017.01.15
Приостановка работы хука


15-1453673352
Jeer
2016-01-25 01:09
2017.01.15
С днем Штурмана ВМФ!


2-1430280290
kudatsky
2015-04-29 07:04
2017.01.15
Где находится профайлер AQTime в ХЕ6 ?


15-1452597638
GanibalLector
2016-01-12 14:20
2017.01.15
PDF


3-1311236769
yurikon
2011-07-21 12:26
2017.01.15
Метод TADODataSet.Seek