Система номера номерів


26

На думку великих викликів, я вважав, що це може бути цікавим.

У цьому виклику ми будемо використовувати систему номерів залишків (RNS) для виконання додавання, віднімання та множення на великі цілі числа.

Що таке RNS

RNS - це один із багатьох способів, які люди розробили для ідентифікації цілих чисел. У цій системі числа представлені послідовністю залишків (які є результатами після операції з модулем (тобто решта після цілого поділу)). У цій системі кожне ціле число має багато уявлень. Щоб все було просто, ми обмежимось так, щоб кожне ціле число було унікально представлене. Я думаю, що простіше описати те, що відбувається конкретним прикладом.

Давайте розглянемо перші три простих числа: 2, 3, 5. У системі RNS ми можемо використовувати ці три числа, щоб однозначно представити будь-яке число, яке менше 2 * 3 * 5 = 30, використовуючи залишки. Візьміть 21:

21 менше 30, тому ми можемо представити його, використовуючи результати після моделювання на 2, 3 та 5. (тобто залишок після ділення на цілі числа на 2, 3 та 5)

Ми ототожнюємо 21 із такою послідовністю цілих чисел:

21 ~ {21 mod 2, 21 mod 3, 21 mod 5} = {1, 0, 1}

І тому в нашій системі RNS замість "21" ми використовували б {1,0,1}.

Загалом, з урахуванням цілого числа n , ми представляємо n як { n mod 2, ..., n mod p_k }, де p_k - найменший простий, такий, що n менше, ніж добуток усіх простих чисел p = p .

Інший приклад, скажімо, у нас 3412. Тут нам потрібно використовувати 2,3,5,7,11,13, оскільки 2*3*5*7*11*13=30030тоді, 2*3*5*7*11=2310це занадто мало.

3412 ~ {3412 mod 2, 3412 mod 3, 3412, mod 5, ..., 3412 mod 13} = {0, 1, 2, 3, 2, 6}

Ви помічаєте, що за допомогою цієї системи ми можемо представити дуже велику кількість порівняно безболісно. Використовуючи {1, 2, 3, 4, 5, 6, 7, 8, ...} залишки, ми можемо представити числа до {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} відповідно. ( Ось серія )

Наше завдання

Ми будемо використовувати ці залишки для виконання значень +, - і * у великій кількості. Я опишу ці процеси нижче. На даний момент тут представлені специфікації введення та виведення.

Вхідні дані

Вам будуть дані два (потенційно дуже великі) числа за допомогою аргументу stdin або функції. Вони будуть задані у вигляді рядків основи 10 цифр.

Для подальшого окреслення проблеми ми називаємо перший вхід nі другий m. Припустимо, n> m> = 0 .

Вам також буде дано +або -або *вказати операцію для виконання.

Вихідні дані

Нехай x - ціле число. Ми будемо використовувати [ x ] для позначення RNS-представлення, описаного вище x .

Ви повинні вивести [n] <operator> [m] = [result]

Як виконувати операції в RNS

Ці операції відносно прості. З огляду на два числа в позначеннях RNS, щоб їх додати, відняти або помножити, просто виконайте задані операції компонентно, а потім візьміть модуль.

тобто

{1, 2, 3} + {1, 1, 4} = {(1 + 1) mod 2, (2 + 1) mod 3, (3 + 4) mod 5} = {0, 0, 2}

Зауважте, що якщо кількість залишків, використаних для представлення двох різних чисел, не однакова, під час виконання операцій вам потрібно буде збільшити "коротше" число, щоб воно було однаковим числом залишків. Далі йде той самий процес. Для прикладу див. Тестові приклади.

Те ж саме, якщо для результату потрібно більше залишків, ніж будь-який вхід. Тоді обидва входи потрібно "розширити".

Важливі деталі

  • Ми будемо мати справу з великою кількістю тут, але не довільно великою. Ми будемо нести відповідальність за цифри, що відповідають добутку перших 100 прайменів (див. Нижче). З цією метою ви отримуєте перші 100 праймерів безкоштовно (без байтових витрат) . Ви можете вставити їх у масив, який називається, pабо щось ідіоматичне для вашої мови, а потім відняти кількість байтів, використаних для ініціювання цього масиву, від остаточного підсумкового. Звичайно, це означає, що вони можуть бути жорстко закодовані або ви можете використовувати вбудований для їх генерації.

  • Якщо з чиєїсь причини це ціле представлення за замовчуванням, яке використовується у вашій мові. Це добре.

  • Ви не можете використовувати будь-який тип довільного точного цілого числа, якщо це не є мовою за замовчуванням. Якщо це за замовчуванням, ви можете не використовувати його для зберігання цілих чисел, які зазвичай не вміщуються в 64 біти.

  • Щоб було зрозуміло, кожне ціле число завжди буде представлено з найменшими можливими залишками. Це стосується і вводу, і виводу.

  • Я думаю, що інші характеристики повинні запобігати цьому, але бути зайвими: ви можете не виконувати задану операцію на входах, а потім, а потім змінити все на RNS, а потім вивести. Ви повинні змінити входи в RNS, а потім виконати операції з отримання виводу.

Випробування

  1. Вхід:

n = 10
m = 4
+

Вихід:

{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }

Пояснення:

Спочатку змініть кожне число на його RNS-представлення, як описано вище:

10 ~ {0,1,0}і 4 ~ {0,1}. Зауважте, що коли ми хочемо робити складові, то вони 10мають більше компонентів, ніж 4. Тому ми повинні "розширити" більш коротку кількість. Тож ми коротко напишемо 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Тепер приступаємо до додавання, а потім беремо модуль.

  1. Вхідні дані
n=28
m=18
+

Вихід:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Введення (я мажу обличчя на клавіатурі)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Вихідні дані (розбиті на окремі рядки для читабельності):

[1, 2, 3, 6, 2, 10, 2, 1, 12, 16, 7, 15, 34, 29, 31, 5, 55, 32, 66, 61, 3, 76, 52, 14, 65, 44, 99, 57 ] 
* 
[1, 0, 3, 3, 4, 8, 9, 10, 8, 0 ] 
= 
[1, 0, 4, 4, 8, 2, 1, 10, 4, 0, 17, 7, 27, 21, 44, 51, 56, 9, 6, 9, 12, 0, 52, 36, 43, 68, 99, 24, 96, 39, 96, 66, 125] 

nвимагає 28 праймів. mвимагає 10. n*mвимагає 33.

  1. Вхідні дані
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Вихід:

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 509]
-
[0, 2, 1, 6, 1, 12, 11, 18, 14, 28, 21, 36, 37, 42, 16, 52, 41, 60, 16, 70, 49, 78, 80, 88, 49, 100, 13, 106, 4, 112, 68, 130, 36, 138, 37, 150, 0, 162, 8, 172, 163, 180, 18, 192, 129, 198, 135, 222, 78, 228, 90, 238, 57, 250, 36, 262, 87, 270, 206, 280, 193, 292, 253, 310, 224, 316, 57, 336, 48, 348]
=
[0, 1, 4, 1, 10, 1, 6, 1, 9, 1, 10, 1, 4, 1, 31, 1, 18, 1, 51, 1, 24, 1, 3, 1, 48, 1, 90, 1, 105, 1, 59, 1, 101, 1, 112, 1, 0, 1, 159, 1, 16, 1, 173, 1, 68, 1, 76, 1, 149, 1, 143, 1, 184, 1, 221, 1, 182, 1, 71, 1, 90, 1, 54, 1, 89, 1, 274, 1, 299, 1, 266, 1, 228, 1, 340, 1, 170, 1, 107, 1, 340, 1, 88, 1, 157, 1, 143, 1, 22, 1, 22, 1, 58, 1, 296, 1, 371, 1, 140]

nвикористовує 100 праймерів. mвикористовує 70 праймів. n-mвикористовує 99 прайменів.

Я перевірив їх, використовуючи ChineseRemвбудовану реалізацію китайської теореми Remainder на GAP (яка в основному приймає RNS-числа та змінює їх на 10 чисел). Я вважаю, що вони правильні. Якщо щось здається рибним, будь ласка, дайте мені знати.


Для тих, хто дбає, продукт перших 100 праймерів - це:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Це число на 1 більше, ніж максимальне число, яке ми можемо представити, використовуючи дану систему (і 100 простих обмежень).

Дещо пов’язане


Я думаю, що виконання операції - це далеко не найважча складова, для якої я відчуваю дивно цю проблему.
njpipeorgan

@njpipeorgan Я згоден, виконувати операцію, наприклад, (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))у ES6. Я думаю, що найважча частина - це, мабуть, пошук кількості простих ліній, необхідних для представлення результату, не використовуючи арифметику довільної точності, хоча подальше перетворення в RNS не зовсім тривіальне.
Ніл

Чи можу я мати такий вхід ( 1234,1234,+)?
клісмік

@derpfacePython так, функції також прийнятні
Ліам

"просто виконайте задані операції компонентно" - тоді звідки беруться додаткові компоненти на виході?
smls

Відповіді:


6

GAP

Деякі відомості: Я визнаю, що коли я створював це запитання, так багато місяців тому у мене не було способу вирішення важкої частини цього питання: визначення правильної кількості простих ліній, які слід використати. На цьому сайті є дуже багато розумних людей, і я дійсно сподівався, що хтось придумає спосіб зробити це досить швидко. Однак, оскільки цього не сталося, я навіть не був впевнений, що вирішити цю проблему справді можливо. Отже, мені довелося витратити час, щоб розробити метод. Я вважаю, що те, що я зробив, не порушує жодне з правил цього завдання, я, звичайно, хотів би, щоб це перевірили факти.

Я також трохи шкодую про вибір оскільки рішення дещо глибші, ніж зазвичай відповідають формату тегів. Сказавши це, щоб дотримуватися правил сайту, внизу цієї публікації є "гольф" версія мого рішення.


Код

### The first 100 primes;
primes := Primes{[1..100]};

### In many of the functions below, the 'string' variable is a string of digits
###


### Returns the 'index' digit of 'string' as an integer
GetValueAsInt := function(string, index) 
    return IntChar(string[index]) - 48;
end;

### Used in the 'modulus' function. See that comment for more information. 
### Calculates the contribution to the modulus of a digit 'digit' in the 10^power place.
### 'integer' is the modulus
digit_contribution := function(digit, integer, power)
    local result, i;
    result := 1;
    for i in [0..power-1] do
        result := ( result * (10 mod integer) ) mod integer;
    od;
    result := (result * (digit mod integer) ) mod integer;
    return result;
end;

### This modulus function is used to calculate the modulus of large numbers without storing them
##### as large numbers.
### It does so by breaking them into digits, and calculating the contribution of each digit.
### e.g. 1234 mod 5 = (1000 mod 5)(1 mod 5) + (200 mod 5)(2 mod 5) + (10 mod 5)(3 mod 5) + (4 mod 5)
### It actually mods after every calculation to ensure that we never get a number larger
##### than the modulus ('integer') squared, which will never be even close to 10^64-1
modulus := function(string, integer)
    local i, result, digit, len;
    len := Length(string);
    result := 0;
    for i in [1..len] do
        digit :=  IntChar(string[i]) -48;
        result := ( result + digit_contribution(digit, integer, len-i) )  mod integer;
    od;
    return result;
end;

### This returns the product of the first i-1 primes (mod j). It must not (and does not)
##### ever store an integer larger than 2^64-1
phi_i := function(i,j)
    local index, result;
    result := 1;
    for index in [1..i-1] do
        result := ( result * primes[index] ) mod primes[j];
    od;
    return result;
end;

### Calculates the first residues of 'string' mod the first 100 primes
get_residues := function(string) 
    local p, result;
    result := [];
    for p in primes do
        Add( result, modulus(string, p) );  
    od; 
    return result;
end;

### Gets the ith element in the partial_chinese array, given the previous elements
### See the explanation section and partial_chinese function for more info
get_partial_i := function( i, residues, previous_array )
    local index, result;
    result := residues[i];
    for index in [1..Length(previous_array)] do
        result := ( result - previous_array[index]*phi_i(index,i) ) mod primes[i]; 
    od;     
    result := ( result / phi_i(i,i) ) mod primes[i];
    return result;
end;

### returns an array such that the sum of prod_primes(i)*array[i] is equal to the integer value
##### that is represented by the residues. (It basically just does the CRT without
##### actually summing everything.) prod_primes(i) is the product of the first i-1 primes 
### See the explanation for a bit more info
### This is what allows us to determine the minimal number of primes to represent a RNS number
partial_chinese := function( string )
    local array, i, residues;
    residues := get_residues(string);
    array := [];        
    for i in [1 .. Length(primes)] do
        Add( array, get_partial_i( i, residues, array ) );
    od;
    return array;   
end;

### Same as partial_chinese but takes input in a different form.
partial_chinese_from_residues := function(residues)
    local array, i;
    array := [];        
    for i in [1 .. Length(primes)] do
        Add( array, get_partial_i( i, residues, array ) );
    od;
    return array;
end;

### gives you the number of primes needed to represent an integer. Basically asks how 
##### many trailing zeros there are in the chinese array.
get_size := function(string)
    local array, i, len, result;
    array := partial_chinese(string);
    len := Length(array);
    for i in [0..len-1] do
        if  not (array[len-i] = 0) then
            return len -i;
        fi; 
    od; 
    Print("ERROR: get_size().\n");
    return 0;
end;

### Same as above but with different input format
get_size_from_residues := function(residues)
    local array, i, len, result;
    array := partial_chinese_from_residues(residues);
    len := Length(array);
    for i in [0..len-1] do
        if  not (array[len-i] = 0) then
            return len -i;
        fi; 
    od; 
    Print("ERROR: get_size().\n");
    return 0;
end;

### the actual function. inputs are all strings
f := function(in1, in2, opperation)
    local residues_1, residues_2, residues_result, i;
    residues_1 := get_residues(in1);
    residues_2 := get_residues(in2);
    residues_result := [];
    if opperation = "+" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] + residues_2[i] ) mod primes[i]);
        od;     
    elif opperation = "*" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] * residues_2[i] ) mod primes[i]);
        od;     
    elif opperation = "-" then
        for i in [1..Length(primes)] do
            Add( residues_result, ( residues_1[i] - residues_2[i] ) mod primes[i]);
        od;     
    fi;
    Print(residues_1{[1..get_size(in1)]}, " ", opperation, " ", residues_2{[1..get_size(in2)]}, " = ", residues_result{[1..get_size_from_residues(residues_result)]} );
end;

Пояснення:

Для початку ми обчислюємо всі 100 залишків для обох входів. Робимо це за допомогою modulusфункції в коді. Я намагався бути обережним, щоб ми використовували вбудовану modфункцію після кожного кроку. Це гарантує, що ми ніколи не матимемо числа, яке більше, ніж на 540^21, менше 100-го простого квадрата.

Після того як у нас є всі залишки, ми можемо виконати дану операцію і modкожен запис знову. Тепер у нас є унікальний позначок результату, але нам потрібно визначити мінімальну кількість записів, які ми повинні використовувати для представлення результату та кожного з входів.

Зрозуміти, скільки залишків нам насправді потрібно - це найважча частина цієї проблеми. Щоб визначити це, ми виконуємо більшість кроків китайської теорії залишку (CRT). Однак ми, очевидно, повинні вносити зміни, щоб ми не закінчилися надто великими числами.

Нехай prod(i)буде сума перших i-1простих чисел. Наприклад,

prod(1) = 1
prod(2) = 2
prod(3) = 6
prod(4) = 30
etc

Нехай Xбуде цілим числом. Нехай {r_i}будуть залишки X, тобто

r_i = X mod p_i

Де p_iце iй прем'єр. Це 1<i<=100в нашому випадку.

Тепер ми будемо використовувати CRT , щоб знайти послідовність {u_i}таким чином, що сума по iвід prod(i) * u_iдорівнює X. Зауважимо, що кожен u_iтакож технічно є залишком, як u_i < p_i. Крім того, якщо X < prod(i)тоді u_i = 0. Це має вирішальне значення. Це означає, що, досліджуючи остаточні нулі, ми можемо визначити, скільки залишків r_iнасправді потрібно представити Xв RNS.

Якщо ви хочете вивчити деякі послідовності u_i, partial_chineseфункція повертає u_iпослідовність.

Заплутавшись з CRT, я зміг знайти рекурсивну формулу u_iзначень, вирішивши питання визначення кількості залишків.

Формула така:

u_i = [ r_i - SUM ] / prod(i)       (mod p_i)

Де SUMце сума по j in [1,i)з u_j * prod(i).

Звичайно, prod(i)його в деяких випадках неможливо обчислити, оскільки він занадто великий. Для цього я використав phi_iфункцію. Ця функція повертається prod(j) (mod p_i). Це відбувається modна кожному кроці, тому ми ніколи насправді не обчислюємо щось занадто велике.

Якщо вам цікаво, звідки походить ця формула, я б рекомендував опрацювати кілька прикладів CRT, які можна знайти на сторінці wikipedia .

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


"Гольф" Код, 2621 байт

primes:=Primes{[1..100]};GetValueAsInt:=function(string,index)return IntChar(string[index])-48;end;digit_contribution := function(digit, integer, power)local result, i;result:=1;for i in [0..power-1] do result := ( result * (10 mod integer) ) mod integer;od;result:=(result*(digit mod integer) ) mod integer;return result;end;modulus:=function(string, integer)local i,result,digit,len;len:=Length(string);result:=0;for i in [1..len] do digit:= IntChar(string[i])-48;result:=(result+digit_contribution(digit,integer,len-i)) mod integer;od;return result;end;phi_i:=function(i,j)local index,result;result:=1;for index in [1..i-1] do result:=(result*primes[index] ) mod primes[j];od;return result;end;get_residues:=function(string) local p,result;result:=[];for p in primes do Add(result,modulus(string,p));od;return result;end;get_partial_i:=function(i,residues,previous_array)local index,result;result:=residues[i];for index in [1..Length(previous_array)] do result:=(result-previous_array[index]*phi_i(index,i) ) mod primes[i];od;result:=(result/phi_i(i,i)) mod primes[i];return result;end;partial_chinese:=function(string)local array,i,residues;residues:=get_residues(string);array:=[];for i in [1 .. Length(primes)] do Add(array,get_partial_i(i,residues,array));od;return array;end;partial_chinese_from_residues:=function(residues)local array,i;array:=[];for i in [1..Length(primes)] do Add(array,get_partial_i(i,residues,array));od;return array;end;get_size:=function(string)local array,i,len,result;array:=partial_chinese(string);len:=Length(array);for i in [0..len-1] do if not (array[len-i] = 0) then return len-i;fi;od;Print("ERROR: get_size().\n");return 0;end;get_size_from_residues:=function(residues)local array,i,len,result;array:=partial_chinese_from_residues(residues);len:=Length(array);for i in [0..len-1] do if not (array[len-i]=0) then return len-i;fi;od;Print("ERROR: get_size().\n");return 0;end;f:=function(in1,in2,opperation)local residues_1,residues_2,residues_result,i;residues_1:=get_residues(in1);residues_2:=get_residues(in2);residues_result:=[];if opperation = "+" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]+residues_2[i] ) mod primes[i]);od;elif opperation = "*" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]*residues_2[i])mod primes[i]);od;elif opperation = "-" then for i in [1..Length(primes)] do Add(residues_result,(residues_1[i]-residues_2[i]) mod primes[i]);od;fi;Print(residues_1{[1..get_size(in1)]}, " ", opperation, " ", residues_2{[1..get_size(in2)]}, " = ", residues_result{[1..get_size_from_residues(residues_result)]} );end;

Я розгублений, тому що звичайний RNS не змінює розміри за потребою, але чи не ви будете згинати правила, обчислюючи розширений 100 залишковий номер від введення, а не лише необхідні розміри, а потім виконуючи операції? "По-перше, змінити кожне число на його RNS-представлення, як описано вище ", для мене означає, що номер "RNS" повинен мати лише залишки, необхідні до того, як щось буде зроблено.
Лінус

Вибачте @Linus, я думав, що вже відповів на це. Я згоден з вами, але я думаю, що потрібні зміни (які я внесу) є відносно тривіальними. Як я бачу, все, що мені потрібно зробити, - це обчислити залишкові довжини входів перед виконанням операції. Використання всіх 100 простих чисел для всіх трьох чисел просто використовує той факт, що всі числа обмежені вище
Ліам

@Linus і у відповідь на ваше перше запитання, як правило, всі числа використовуватимуть однакову кількість залишків. Це зробило б питання набагато простішим
Ліам

2

Математика, не гольф

rns[d_,l_]:=Table[Reap[
    FoldPairList[Sow@QuotientRemainder[10#+#2,Prime@i]&,0,d]
  ][[2,1,-1,2]],{i,l}];
plus[a_,b_]:=Mod[a+b,Prime@Range@Length@a];
subtract[a_,b_]:=Mod[a-b,Prime@Range@Length@a];
times[a_,b_]:=Mod[a b,Prime@Range@Length@a];
mag[f_]:=LengthWhile[FoldList[#/#2&,f,Prime@Range@100],#>1.1&];
ext[m_,n_,i_]:=Fold[Mod[1##,Prime@i]&,m,Prime@Range@n];
multi[e_,p_,t_]:=Tr@Position[Mod[e Range@p,p],p-t];
appx[d_] := N@FromDigits[{d~Take~UpTo[6], Length@d}]
  • Функція rns[d_,l_]перетворює ціле число базового 10 dу ціле число довжини RNS l.

  • Функція plus/ times/ subtractдодавання / множення / віднімання одного цілого RNS до / від іншого, обидва мають однакову довжину.

  • Функція mag[f_]оцінює приблизну величину числа плаваючої точки fз точки зору нижньої межі довжини її подання на RNS.

  • Функція ext[m_,n_,i_]з'ясовує залишок від поділу добутку на mі Prime[Range@n]на Prime[i].

  • Функція multi[e_,p_,t_]дає найменший множник, що mзадовольняє цеDivisible[m*e+t,p]

  • Функція appx[d_]приймає перші 6цифри десяткового цілого числа і дає її приблизне значення з плаваючою комою.


За допомогою функцій, наведених вище, тепер ми маємо змогу вирішити складну задачу - визначити довжину результату .

По-перше, я повинен уточнити, що визначити довжину RNS цілого числа непроста задача. Для малих цілих чисел ми можемо порівняти їх безпосередньо з добутками простих чисел. Але для дуже великих цілих чисел, оскільки заборонено обчислювати добуток простих величин нескінченно точним, таке порівняння вже не працює.

Наприклад, за умови , що продукт простого 1To 30є 3.16*10^46, довжина РНС цілих чисел навколо 3.16*10^46може бути можлива 29або 30. Функція magдасть 29орієнтир для цих цілих чисел, показуючи, що і те, 29і 30можливо.

Знаючи величину, ми просто представляємо ціле число відповідно до цієї величини безпосередньо, сподіваючись обчислити його справжню довжину. Хитрість тут полягає в тому, щоб додати кілька вихідних чисел до вихідного числа та змінити його представлення RNS, поки представлення не дорівнює нулю.

Наприклад, mag[211.]є 4, і його довжина 4представляє {1, 1, 1, 1}.

step 1:   {1,1,1,1} -> {0,2,2,2}  by adding  (1) * 1 = 1
step 2:   {0,2,2,2} -> {0,0,1,6}  by adding  (2) * 2 = 4
step 3:   {0,0,1,6} -> {0,0,0,2}  by adding  (2*3) * 4 = 24
step 4:   {0,0,0,2} -> {0,0,0,0}  by adding  (2*3*5) * 6 = 180
step 5:   calculate 211 + (1 + 4 + 24 + 180) ~ 420

Додавши деяке число, ми збільшуємо 211до найменшого числа, яке ділиться на 210( 2*3*5*7). І тепер ми приходимо до висновку , що початкове число більше 210, так як 420рівні «приблизно» дворазовий 210. Не важко уявити, що якщо ми почнемо з цього 209, то остаточне число - «приблизно» 210.

Функція length[f_,n_]приймає значення плаваючої точки fдля оцінки величини і виправляє її на основі подання на RNS n.

length[f_,n_]:=With[{g=mag@f},
    g+If[#==0,1,Round[(#+f)/Times@@Prime@Range@g]-1]&[
      FoldList[Times,1.,Prime[Range[g-1]]].
      FoldPairList[
        Block[{i=#2,m},
          {m=multi[ext[1,i-1,i],Prime@i,Part@##],rnsPlus[#,ext[m,i-1,#]&/@Range[g]]}
        ]&,n,Range[g]]]]

Функція rnsOperation[a_,b_,op_,rnsop_]дає rnsop[a,b]і opвідповідає нормальним операціям, які використовуються для отримання приблизних результатів на основі значень з плаваючою комою.

rnsOperation[a_,b_,op_,rnsop_]:=Block[{c=op[appx@a,appx@b],m},
    m=mag[c];m=length[c,rnsop[rns[a,m],rns[b,m]]];rnsop[rns[a,m],rns[b,m]]]

Приклад

rnsOperation[
    IntegerDigits@1231725471982371298419823012819231982571923,
    IntegerDigits@1288488183,
    Times, times]
(* {1,0,4,4,8,2,1,10,4,0,17,7,27,21,44,51,56,9,6,9,12,0,52,36,43,68,99,24,96,39,96,66,125} *)

1
На жаль, правила, викладені в нашому довідковому центрі, вимагають, щоб усі матеріали були серйозним претендентом на виграшні критерії, які використовуються. Для змагань з кодовим гольфом це означає, що всі подання повинні бути зіграні.
Денніс

@Dennis Я знаю про це правило. Однак навіть без гольфу я вважаю, що ця проблема є досить складною і складною, тому вирішення цієї проблеми, а не гольф - моя мета.
njpipeorgan

це може бути не гольф, але чорт короткий порівняно з моєю програмою Java: P, хоча моя програма, ймовірно, набагато швидша.
СподіваюсьДопомога

1
Я відчуваю, що ти здатний пограти у цей гольф
Рохан Джунджунвала

2

Python 3 , 435 байт

Цей виклик вже деякий час є у моєму списку відра, але лише нещодавно: а) я вклав час і увагу, щоб насправді намагатися відповісти; і б) фактично перевірив мою ідею обчислити розмір чисел (і так кількість простих чисел, порівнюючи його з розміром первісних), використовуючи деяку нечесну комбінацію логарифмів і теорему залишків Китаю. На жаль, робота з логарифмами, намагаючись визначити кількість простих ліній, яких, наприклад, large_primorial + 3вимагає, означала, що мені доводиться знаходити способи вирішити проблеми з плаваючою комою.

І ось, це відповідь відповіді Ліама .

Спробуйте в Інтернеті!

from functools import reduce as R
G=range
d=lambda s:[R(lambda z,c:(z*10+int(c))%q,s,0)for q in p]
h=lambda j,i:R(lambda z,q:z*q%p[i],p[:j],1)
def s(r):
 a=[];z=99
 for i in G(100):
  P=p[i];u=r[i]
  for j in G(len(a)):u=(u-a[j]*h(j,i))%P
  for k in G(1,P):
   if h(i,i)*k%P<2:break
  a+=u*k%P,
 while(a[z]<1)*z:z-=1
 return r[:z+1]
def f(a,b,n):u=d(a);v=d(b);print(s(u),n,s(v),'=',s([eval(str(u[i])+n+str(v[i]))%p[i]for i in G(100)]))

Пояснення

Намагаючись передати відповідь Ліама, я особисто виявив, що дане пояснення було заплутаним, тому це моя спроба пояснити його алгоритм.

Спочатку ми отримуємо залишки nта m.

res1 = get_residues(n)
res2 = get_residues(m)

Це передбачає перетворення всіх цифр вхідних рядків та перетворення їх на модулі чисел кожного з наших простих чисел, наприклад, для 28, [(20 + 8) mod 2, (20 + 8) mod 3, (20 + 8) mod 5, etc]

def get_residues(string):
    result = []
    for p in primes:
        result.append(reduce(lambda z, c:(z*10+int(c)) % p, string, 0))

Потім додаємо, множимо або віднімаємо залишки попарно, використовуючи eval()

result = []
for i in range(len(primes)):
    result.append((eval(str(res1[i]) + op + str(res2[i])) % primes[i])

Тоді ми отримуємо розміри наших залишків, тобто мінімальна кількість необхідних нам прайменів.

size1 = get_size(res1)
size2 = get_size(res2)
size3 = get_size(result)

Отримання розмірів - найскладніша та найвибагливіша частина. Ми використовуємо partial_chineseфункцію, яка отримує нам нашу послідовність, u_iщоб визначити розмір. Більше про u_iмить.

def get_size(residues):
    array = partial_chinese(residues)
    size = len(residues)-1
    while array[size] == 0 and size:
        size -= 1
    return size+1  # to prevent off-by-one errors from 0-indexing

Послідовність u_iобчислюється, беручи кожен залишок r_i, віднімаючи суму u_j * primorial(j) for j in [1, i), а потім dividingпо primorial(i)всій модулі primes[i]. Тобто u_i = (r_i - SUM) / primorial(i). Детальніше про наші первісні та подільні функції за мить.

def partial_chinese(residues):
    array = []
    for i in range(len(primes)):
        array.append(get_partial_i(i, residues, array))
    return array

def get_partial_i(i, residues, previous_array):
    result = residues[i]
    for j in range(len(previous_array)):
        result = (result - previous_array[j] * phi_i(j, i)) % primes[i]
    result = result * inverse(phi_i(i, i), primes[i]) % primes[i]
    return result

phi_i(j, i)обчислює primorial(j) mod primes[i]. Поділ по модулю будь-який прайм pлегко реалізується, перевіряючи мультиплікативні звороти вручну, оскільки ми можемо бути впевнені, що це можливоu_i буде 0 <= u_i < pгарантовано бути взаємно прості з р і т гарантується мультиплікативний зворотний.

def phi_i(j, i):
    return reduce(lambda z, q: z * q % primes[i], primes[:j], 1)

def inverse(n, p):
    for i in range(1, p):
        if n * i % p == 1:
            return i

З усім, що зроблено, ми друкуємо наш рядок і ми закінчили.

print(res1[:size1], op, res2[:size2], "=", result[:size3])

Що далі

Це було цікаво здійснити. Я все ще хочу дізнатися, чи можу я використовувати логарифми якимось чином в іншій відповіді. І я хотів би реалізувати цей код або щось на зразок функціональної мови для гольфу, наприклад, APL або Jelly. Будь-які пропозиції щодо коригування та виправлення гольфу!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.