Код из #58 работает не во всех случаях - взял от балды dword [q] = dword [q+4] = 9ABCDEF0h, правильный остаток 0B5765398h, а код выдаёт 0B5865397h. Код из #57 не тестил.
А может я отбираю себе код на конкурсной основе =))) Мне сначала нужно посмотреть портфолио, типа как это быстро и круто в сравнении с другими претендентами.
2persicum Тогда облом -- я тоже отбираю работодателей на конкурсной основе =))) 2diamond Не стоит. Набегает ошибка округления из-за того, что используется 10010010000000000h, а не на 1001000FF0FE0FD11h, в случае, если edx > 0EFFFFFFFh ошибка замечена и компенсирована -1, но, оказывается, я плохо продумал алго, пойду перечитаю Уоррена...
Mikl___ советую подойти к решению задачи более творчески. пост 27 использовал естественное разбиение числа на 64 бит как a*2^32 + b, но можно также представить его как a*2^20 + b. Тогда для нахождения искомого остатка достаточно разделить a на FFF, тут пригодятся и остаточек и частное от этого деления, если так нравится делить по Уоррену.
Mikl___ Метод поста н27 будет хорош для числа FFF00001, но будет плох для числа C0000001. Советую изучить разнообразные трюки именно для взятия остатков.
Код (Text): mov edx,[q+4] mov eax,[q] ;100000000h/FFF00001h=1,0002442000112558721159827002697=1001000FFh/100000000h= ;=1 + 1/1000h + 1/1000000h - 1/100000000h mov ecx,edx mov esi,eax mov ebx,eax sub ebx,edx sbb ecx,0 shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx ;частное в ecx -- чтобы получить остаток умножаем ecx на 0FFF00001h и отнимаем от q ;ecx*0FFF00001=ecx*2^32 - ecx*2^20 + ecx, но так как остаток 32-разрядный ;rem = low(q) + ecx*2^20 - ecx sub eax,ecx shl ecx,20 add eax,ecx; остаток в eax diamond вроде бы, правильно считает остаток на всем 64-битном диаппазоне от 0 до 0FFFF.FFFF.FFFF.FFFFh, а не только, как div до 0FFF0.0000.FFFF.FFFFh, но если можно, протестируйте еще раз, я уже ни в чем не уверен... persicum Не очень сложно переделать и для C0000001h, D0000001h, F3000001h
Просто значения, взятые от балды, посчитались правильно; тогда взял edx:eax = FFF00001 * ABCDEFAB, прогнал - получил остаток FFF00001 вместо правильного 0. Добавил единицу, получил FFF00002. Добавил вместо единицы 100000h, код выдал остаток 1.
Mikl___ Код поста 67 просто замечательный, совсем другое дело! Я пока не совсем понимаю как он работает, меня от использования шифтов отворачивала необходимость вычитать, но тут идет прибавление . Однако, есть ряд недочетов. 1) может возвращать недобитый остаток, например (p-1)(p-1) выдает p+1 2) где гарантия, что невозможно переполнение в пределах допустимых значений множителей меньше p 3) переполнение можно вызвать явно умножив -1 на -1, однако это не является недостатком процедуры, поскольку -1 лежит вне диапазона Это я говорю про процедуру дополренную вначале mul edx, поскольку мне нужно edx и eax вначале перемножить а уж потом брать остаток
persicum Мне нужно сперва устранить ошибку, обнаруженную diamond, тогда и пункты 1-3, может быть, сами уйдут... А считать должно в пределах всего 64-битного диаппазона, это я с калькулятором проверял
в подтыерждение опасения 2) нашел пример, когда допустимые входные величины вызывают потерю значимости, а именно (p-1)(p-FFFFF) должно преспокойненько давать FFFFF, а получается угадайте что? Лажа...
Код (Text): mov edx,dword ptr q+4 mov eax,dword ptr q mov ecx,edx mov esi,eax mov ebx,eax sub ebx,edx sbb ecx,0 shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx mov edi,ecx ;частное в edi ;чтобы получить остаток умножаем ecx на 0FFF00001h и отнимаем от q ;ecx*0FFF00001=ecx*2^32 - ecx*2^20 + ecx sub eax,ecx shl ecx,20 add eax,ecx mov ecx,0FFF00000h cmp ecx,eax sbb ecx,ecx sub edi,ecx; коррекция частного and ecx,0FFF00001h sub eax,ecx; остаток в eax
MS тоже любит накладывать заплатки... как показывает практика, дыр от этого меньше не становится. Отчётливо видна коррекция предыдущего решения. Снова взял edx:eax = FFF00001 * ABCDEFAB + 100000, снова получил остаток 1 вместо правильного 100000h.
Код (Text): mov edx,[q+4] mov eax,[q] test edx,edx jnz a1; если edx равен нулю ;если edx = 0 и eax < 0FFF00001 тогда остаток = eax ;если edx = 0 и eax >= 0FFF00001 тогда остаток = eax - 0FFF00001 sub eax,0FFF00001h sbb esi,esi and esi,0FFF00001h add eax,esi jmp a2; в eax остаток ;если edx > 0 ищем частное через умножение на ;1,0002442000112558721159827002697=1001000FFh/100000000h a1: mov ecx,edx mov esi,eax mov ebx,eax sub ebx,edx sbb ecx,0 shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx shrd esi,edx,12 shr edx,12 add ebx,esi adc ecx,edx add ebx,80000000h;<-- "округление", которого так не хватало adc ecx,0;<------------┘ ;частное в ecx -- чтобы получить остаток умножаем ecx на 0FFF00001h и отнимаем от q ;ecx*0FFF00001=ecx*2^32 - ecx*2^20 + ecx sub eax,ecx shl ecx,20 add eax,ecx a2:... diamond "Чай, теперь твоя душенька довольна?" (© А.С.Пушкин)
Mikl___ Ну это просто звиндец! Клюв вынул - хвост увяз. Старые дыры ты залечил, не спорю, но попробуй теперь (p-1). А где гарантия, что и через десять постов оно будет абсолютно надежно? Теперь я понял, что значит деление без восстановления =))))))
persicum Если p=0FFF00001, то 1) (p-1)(p-1)/0FFF00001=0FFE0010000000000/0FFF00001 частное 0FFEFFFFF остаток 1 2) (p-1)/0FFF00001=0FFF00000/0FFF00001 частное 0 остаток FFF00000 3) (p-1)(p-FFFFF)/0FFF00001=FFD00201FFE00000/0FFF00001 частное 0FFE00001 остаток 0FFFFF
Если цель всего этого - арифметика по модулю p и над числами производится много последовательных операций по модулю p, то можно воспользоваться трюком из статьи Монтгомери http://www2.itu.edu.tr/~orssi/dersler/cryptography/Montgomery.pdf : можно так кодировать вычеты по mod p, что операции сложения/вычитания устроены так же, как и при стандартном представлении, а умножение реализуется проще. При этом все константы и все входные данные нужно перекодировать (это некоторая перестановка на множестве вычетов), а все выходные данные - перекодировать обратно. Применительно к p = 0xFFF00001 три операции кодирования/умножения/декодирования выглядят так: Код (Text): start: mov eax, 0x12345678 ; первый множитель в стандартном представлении call input2coded push eax mov eax, 0x9ABCDEF0 ; второй множитель в стандартном представлении call input2coded pop edx call coded_mul call coded2output ; eax = residue ret coded2output: ; eax -> eax xor edx, edx jmp coded_mul_doit input2coded: ; eax -> eax mov edx, 0x0FDFFF01 coded_mul: ; eax*edx -> eax mul edx coded_mul_doit: mov esi, eax shl eax, 20 add eax, esi xor esi, esi mov ebx, eax shld esi, eax, 20 shl ebx, 20 mov edi, eax sub eax, ebx sbb edi, esi sub edx, edi sbb eax, eax and eax, 0xFFF00001 add eax, edx ret Если производится несколько операций по mod p подряд, то между этими операциями кодировать/декодировать ничего не нужно.
Про Монтгомери мысль была, но останавливают такие соображения 1) лень переводить числа в другой формат 2) грехх юзать универсальный метод с почти-Мерсеновским числом 2^32-2^20+1 !!! 3) код #27 работает на умножениях без деления, как и Монтгомери 4) murder , SSE2 ускоряет его в ТРИ раза ! 5) я не верю, что в Монтгомери можно складывать без компенсации. Если обычным порядком у меня произошло переполнение при сложении то я отниму p и все дела. А если у меня произойдет переполнение при сложении измененных данных, я разумеется не смогу взять просто младшие 32-бит, нужна такая же компинсация как и при умножении... Мне так кажется.
1) Код уже приведён. 2) Пока что не видно работающих алгоритмов, использующих специфику числа, а то, что есть, заметно длиннее кода из #78. 3) И использует цикл, а цикл - это ветвление, а ветвлений рекомендуется избегать. 4) Это говорит скорее о тормознутости исходного кода 5) Даже при обычном порядке если произошло переполнение при сложении, нужно это специально обрабатывать - нельзя просто взять младшие 32 бит (нужно ещё вычесть p). В методе Монтгомери всё точно так же.