YadryaraМожно, глядишь и сам лучше разберусь.
Для примера возьмём паттерн 0,2,6,8 (из темы про асм). Он имеет следующие допустимые остатки:
Код:
? v=[0,2,6,8]; forprime(p=2,15,m=setminus(vector(p,i,i-1),Set(-v%p)); print(p,": ",m,", len=",#m);)
2: [1], len=1
3: [2], len=1
5: [1], len=1
7: [2, 3, 4], len=3
11: [1, 2, 4, 6, 7, 8, 10], len=7
13: [1, 2, 3, 4, 6, 8, 9, 10, 12], len=9
Или Вам получение m[] непонятно? Но кажется я уже объяснял эту конструкцию ещё когда её только придумал (возможно не в этой теме). Да и в общем не суть как именно получать список допустимых остатков, главное что это можно сделать и лишь один раз при запуске или вообще просто хранить заранее посчитанные.
Узнаем какой единственный остаток по модулю 2*3*5=30:
Код:
? chinese([Mod(1,2),Mod(2,3),Mod(1,5)])
%1 = Mod(11, 30)
Т.е. в таблице будет пока единственное число 11.
Посмотрим что за 3 остатка по модулю 30*7=210 нам надо получить:
Код:
? foreach([2,3,4],y,print1(chinese(Mod(11,30),Mod(y,7)),", "))
Mod(191, 210), Mod(101, 210), Mod(11, 210),
Т.е. в таблице должны получиться числа 191,101,11 в любом порядке.
Это пока были образцовые данные, те что и надо получить.
Вспоминаем формулу r[]=x[]+Mx*(((y[]-x[])*d)%My). Здесь x[]=[11], Mx=30, y[]=[2,3,4], My=7, вычисляем d=Mod(1/Mx,My)=Mod(4,7)=4. Пробуем получить:
Код:
? 11+30*(((2-11)*4)%7)
%1 = 191
? 11+30*(((3-11)*4)%7)
%2 = 101
? 11+30*(((4-11)*4)%7)
%3 = 11
Получили, КТО по такой формуле работает (ну никто и не сомневался, и в вики и вообще везде она и приведена под названием алгоритм Гарнера).
А теперь попробую (самому интересно) получить то же самое через A[]=Mx*((y[]*d)%My) и B[]=Mx*((x[]*d)%My), так как все y[] меньше единственного x[], то условие y[]<x[] выполняется всегда и значит четвертое слагаемое должно быть всегда:
Код:
? 11+30*((2*4)%7)-30*((11*4)%7)+30*7
%1 = 191
? 11+30*((3*4)%7)-30*((11*4)%7)+30*7
%2 = 311
? 11+30*((4*4)%7)-30*((11*4)%7)+30*7
%3 = 221
А вот и глюк. Условие y[]<x[] вовсе не равнозначно условию A[]<B[]. Хотя по модулю 210 получили ровно те же числа, почти правильные, надо лишь с условным слагаемым разобраться.
Посмотрим чему равны A[] и B[]:
Код:
? B=30*(([11]*4)%7)
%1 = [60]
? A=30*(([2,3,4]*4)%7)
%2 = [30, 150, 60]
И действительно, условие A[]<B[] выполняется лишь для первого числа и не выполняется для двух остальных, которые и получились на Mx*My больше правильных.
Т.е. для правильного вычисления КТО по такой формуле, с A[] и B[], надо использовать именно условие A[]<B[], как у меня выше в формуле и было написано. Проверим:
Код:
? foreach([2,3,4],y, A=30*((y*4)%7); B=30*((11*4)%7); print(11+A-B+if(A<B,30*7)); )
191
101
11
Вуаля, с КТО всё получилось.
Дальше речь уже не про КТО, а про мою программу, которой эти числа в общем и не нужны, а нужны остатки от них по модулю например 71:
Код:
? [191,101,11]%71
%1 = [49, 30, 11]
Именно этот вектор надо получить имея на руках x[]%71=[11]%71=[11] и y[]%71=[2,3,4]%71=[2,3,4].
Предполагаем что x[] и B[] и Mx*My и все прочие константы можем посчитать вне цикла, в цикле же будем перебирать лишь y[]. Сразу видим что сам y[] нам нафиг не сдался, в формуле x[]+A[]-B[]+if(A[]<B[],Mx*My) вместо него участвует только A[]=Mx*((y[]*d)%My) и логично именно его и хранить как A[]=[30, 150, 60].
Далее, x[] и B[] внутри цикла по y[] (A[]) не меняются, значит их сумму вычисляем заранее.
А раз нас интересует только остаток всего этого дела по модулю 71, то и сумму и A[]%71=A1[] вычислим заранее.
Приходим к следующей программе:
Код:
? x=11; B=30*((x*4)%7); S=(x+B)%71
%1 = 0
? (30*7)%71
%2 = 68
? A=[30,150,60]; A1=A%71
%3 = [30, 8, 60]
? for(i=1,#A, r=S+A1[i]+if(A[i]<B,68,0); print(r); )
98
8
60
Вообще ни разу не совпадают с требуемыми [49, 30, 11], даже по модулю 71.
Чешу репу, вот же засада, а я уже кинулся код на асме писать ...
Но формула в принципе правильная:
Код:
? for(i=1,#A, r=11%71+A[i]%71-B%71+if(A[i]<B,(30*7)%71,0); print(r); )
49
-41
11
? for(i=1,#A, r=11%71+A[i]%71-B%71+if(A[i]<B,(30*7)%71,0); if(r<0, r+=71); print(r); )
49
30
11
Осталось понять какое из сложений вызывает глюк.
Оказалось объединение x[]-B[] по общему модулю 71:
Код:
? for(i=1,#A, r=(11-B)%71+A1[i]+if(i<2,(30*7)%71,0); if(r<0, r+=71); print(r); )
120
30
82
Но при этом цифры почти правильные - по модулю 71 это именно то что нужно. Т.е. достаточно все два сложения (с A1[] и с if) выполнить по модулю 71 и получим искомое. Развернув цикл и учтя какие индексы дают именно A[]<B[] получим:
Код:
? ((11-60+68)%71+A1[1])%71
%1 = 49
? ((11-60)%71+A1[2])%71
%2 = 30
? ((11-60)%71+A1[3])%71
%3 = 11
Ура! Именно то что нужно.
При этом разумеется левое слагаемое вычисляем заранее, не в цикле перебора A1[] (y[]). А так как мы всегда можем отсортировать A1[], то будет точная граница индекса, до которой слева будет одно слагаемое, а после неё другое, это легко реализуется.
Остаётся ровно одно сложение по модулю, что как уже известно реализуется тремя командами AVX за 1 такт (4 обычных команды c cmov). И именно его и делает текущий выполняемый код. Надо только добавить ему не одно левое слагаемое, а два разных, до границы и после. Границу можно вычислять не в самом цикле, а снаружи, поиском по A[], это быстро.
Заметим что вычисление левого слагаемого по имеющемуся x[] достаточно просто, два деления, два умножения, несколько сложений, поиск в массиве. Всё это займёт ну пусть полтыщи тактов, но выбрав длину y[] (A[], A1[]) достаточно большой чтобы его обработка занимала хотя бы полсотни тысяч тактов, накладные расходы на вычисление левого слагаемого уйдут ниже 1% и ими можно будет пренебречь.
А далее, вовсе не обязательно x[] хранить, его тоже можно получать по КТО из остатков по любым простым кроме используемых в y[],My. КТО же даже в исходном виде x[]+Mx*(((y[]-x[])*d)%My) требует всего одно деление и два умножения, выбирая всегда чтобы и Mx и My на каждом шаге (для каждой пары простых или накопленного вектора и нового произведения простых) помещались в регистры (x64 разумеется) это вычисление довольно быстрое. А если даже и не слишком быстрое, то всегда можно увеличить y[] для внутреннего цикла настолько чтобы его обработка занимала более 99% общего времени, для этого достаточно длин порядка сотен тысяч, в много миллионов уходить не надо.
На этом для меня всё стало ясно, дальше уже оптимизация (ассемблерного) кода.
-- 26.01.2024, 16:13 --Уф, получив по модулю чушь реально подумал всё, кирдык, ещё одна прекрасная идея улетела в туман, нифига не работает ...
Стал судорожно усложнять исходную очевидно работающую формулу КТО буквально по мелким шажкам (большинство из которых показывать уже не стал) и таки обнаружил затык. И при его лечении оказалось я был таки прав изначально, всё получается как надо, код внутреннего цикла укладывается в одно сложение по модулю, как и сейчас. С дополнительным условием, но которое можно вынести и за цикл, скорость оно не испортит. Теперь как это всё реализовать, в коде полно своих заморочек ...