Sistem Nomor Residu

26

Di tengah banyaknya tantangan saya pikir ini mungkin menarik.

Dalam tantangan ini, kita akan menggunakan Residue Number System (RNS) untuk melakukan penambahan, pengurangan, dan penggandaan pada bilangan bulat besar.

Apa itu RNS?

RNS adalah salah satu dari banyak cara yang telah dikembangkan orang untuk mengidentifikasi bilangan bulat. Dalam sistem ini, angka diwakili oleh urutan residu (yang merupakan hasil setelah operasi modulus (yaitu sisa setelah pembagian bilangan bulat)). Dalam sistem ini, setiap integer memiliki banyak representasi. Untuk menjaga hal-hal sederhana, kita akan membatasi hal-hal sehingga setiap bilangan bulat diwakili secara unik. Saya pikir lebih mudah menggambarkan apa yang terjadi dengan contoh nyata.

Mari kita lihat tiga bilangan prima pertama: 2, 3, 5. Dalam sistem RNS, kita dapat menggunakan ketiga bilangan ini untuk secara unik mewakili bilangan apa pun yang kurang dari 2 * 3 * 5 = 30 menggunakan residu. Ambil 21:

21 kurang dari 30, jadi kita dapat mewakilinya menggunakan hasil setelah modding dengan 2, 3, dan 5. (yaitu sisanya setelah integer membaginya dengan 2, 3, dan 5)

Kami akan mengidentifikasi 21 dengan urutan bilangan bulat berikut:

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

Jadi dalam sistem RNS kami, alih-alih "21", kami akan menggunakan {1,0,1}.

Secara umum diberi bilangan bulat n , kami mewakili n sebagai { n mod 2, ..., n mod p_k } di mana p_k adalah bilangan terkecil sehingga n lebih kecil dari produk semua bilangan prima kurang dari atau sama dengan p_k .

Contoh lain, katakanlah kita memiliki 3412. Kita perlu menggunakan 2,3,5,7,11,13 di sini karena 2*3*5*7*11*13=30030sedangkan, 2*3*5*7*11=2310yang terlalu kecil.

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

Anda perhatikan bahwa dengan menggunakan sistem ini kami dapat mewakili jumlah yang sangat besar secara relatif tanpa rasa sakit. Dengan menggunakan residu {1, 2, 3, 4, 5, 6, 7, 8,}, kita dapat merepresentasikan angka hingga {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} masing-masing. ( Ini adalah seri )

Tugas kita

Kami akan menggunakan residu ini untuk melakukan +, -, dan * dalam jumlah besar. Saya akan menjelaskan proses-proses di bawah ini. Untuk saat ini di sini adalah spesifikasi input dan output.

Memasukkan

Anda akan diberikan dua angka (berpotensi sangat besar) melalui argumen stdin atau fungsi. Mereka akan diberikan sebagai string basis 10 digit.

Untuk keperluan menguraikan masalah lebih lanjut, kami memanggil input pertama ndan kedua m. Asumsikan n> m> = 0 .

Anda juga akan diberikan +atau -atau *untuk menunjukkan operasi untuk melakukan.

Keluaran

Biarkan x menjadi bilangan bulat. Kami akan menggunakan [ x ] untuk merujuk ke representasi RNS yang dijelaskan di atas x .

Anda akan menghasilkan [n] <operator> [m] = [result]

Cara melakukan operasi di RNS

Operasi ini relatif sederhana. Diberi dua angka dalam notasi RNS, untuk menambah, mengurangi, atau mengalikannya, cukup lakukan operasi komponen yang diberikan dengan bijaksana dan kemudian ambil modulus.

yaitu

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

Perhatikan bahwa jika jumlah residu yang digunakan untuk mewakili dua angka berbeda tidak sama, saat melakukan operasi, Anda perlu memperpanjang angka "lebih pendek" sehingga memiliki jumlah residu yang sama. Ini mengikuti proses yang sama. Lihat contoh uji untuk contoh.

Hal yang sama berlaku jika hasilnya membutuhkan lebih banyak residu daripada input. Maka kedua input harus "diperluas".

Detail Penting

  • Kita akan berhadapan dengan angka-angka besar di sini, tetapi tidak dengan sembarangan besar. Kami akan bertanggung jawab untuk nomor hingga produk dari 100 bilangan prima pertama (lihat di bawah). Untuk tujuan ini, Anda diberikan 100 bilangan prima pertama secara gratis (tanpa biaya byte) . Anda dapat menempelkannya dalam array yang disebut patau sesuatu yang idiomatis untuk bahasa Anda dan kemudian kurangi jumlah byte yang digunakan untuk memulai array ini dari total akhir Anda. Ini tentu saja berarti bahwa mereka mungkin hard-coded atau Anda dapat menggunakan built-in untuk membuatnya.

  • Jika karena alasan tertentu ini adalah representasi integer default yang digunakan dalam bahasa Anda. Ini baik saja.

  • Anda tidak boleh menggunakan tipe Integer Presisi Sewenang-wenang kecuali itu adalah bahasa default Anda. Jika itu adalah default, Anda mungkin tidak menggunakannya untuk menyimpan bilangan bulat yang biasanya tidak muat dalam 64 bit.

  • Agar jelas, setiap bilangan bulat akan selalu diwakili dengan residu sesedikit mungkin. Ini berlaku untuk input dan output.

  • Saya pikir spesifikasi lain harus mencegah hal ini, tetapi menjadi berlebihan: Anda mungkin tidak melakukan operasi yang diberikan pada input dan kemudian mengubah segalanya menjadi RNS dan kemudian output. Anda harus mengubah input ke RNS dan kemudian melakukan operasi untuk menghasilkan output.

Uji Kasus

  1. Memasukkan:

n = 10
m = 4
+

Keluaran:

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

Penjelasan:

Pertama, ubah setiap angka menjadi representasi RNS seperti dijelaskan di atas:

10 ~ {0,1,0}dan 4 ~ {0,1}. Perhatikan bahwa ketika kita ingin melakukan penambahan komponen, itu 10memiliki lebih banyak komponen daripada 4. Karena itu kita harus "memperpanjang" angka yang lebih pendek. Jadi kita akan menulis secara singkat 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Sekarang kita lanjutkan dengan penambahan dan kemudian mengambil modulus.

  1. Memasukkan
n=28
m=18
+

Keluaran:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Input (saya menumbuk wajah saya di keyboard)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Keluaran (dipisah menjadi baris terpisah untuk dibaca):

[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] 

nmembutuhkan 28 bilangan prima. mmembutuhkan 10. n*mmembutuhkan 33.

  1. Memasukkan
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Keluaran:

[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]

nmenggunakan 100 bilangan prima. mmenggunakan 70 bilangan prima. n-mmenggunakan 99 bilangan prima.

Saya memeriksa ini menggunakan ChineseRemimplementasi built-in teorema Remainder Cina pada GAP (yang pada dasarnya mengambil angka RNS dan mengubahnya ke basis 10 integer). Saya percaya mereka benar. Jika ada sesuatu yang mencurigakan, tolong beri tahu saya.


Bagi mereka yang peduli, produk dari 100 bilangan prima pertama adalah:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Angka ini 1 lebih besar dari angka maksimal yang dapat kami wakili menggunakan sistem yang diberikan (dan 100 batasan utama).

Agak terkait

Liam
sumber
Saya pikir melakukan operasi jauh dari menjadi bagian tersulit, yang saya merasa aneh dengan tantangan ini.
njpipeorgan
@ njpipeorgan Saya setuju, melakukan operasi hanya (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))di ES6 misalnya. Saya pikir bagian tersulit mungkin menemukan jumlah bilangan prima yang diperlukan untuk mewakili hasil tanpa menggunakan aritmatika presisi sewenang-wenang, meskipun konversi berikutnya ke RNS tidak sepenuhnya sepele.
Neil
Bisakah saya mendapat input seperti ini ( 1234,1234,+)?
clismique
@derpfacePython ya fungsi juga dapat diterima
Liam
"cukup lakukan operasi yang diberikan komponen-bijaksana" - lalu dari mana komponen tambahan dalam output berasal?
smls

Jawaban:

6

CELAH

Beberapa Latar Belakang: Saya akan mengakui bahwa ketika saya membuat pertanyaan ini, berbulan-bulan yang lalu, saya tidak memiliki metode untuk menyelesaikan bagian sulit dari pertanyaan ini: menentukan jumlah bilangan prima yang tepat untuk digunakan. Kami memiliki banyak orang yang sangat cerdas di situs ini, dan saya benar-benar berharap seseorang akan menemukan cara untuk melakukannya dengan cukup cepat. Namun, karena ini tidak terjadi, saya bahkan tidak yakin bahwa itu benar-benar mungkin untuk menyelesaikan masalah ini. Jadi, saya harus meluangkan waktu untuk menyusun metode. Saya percaya bahwa apa yang telah saya lakukan tidak melanggar aturan dalam tantangan ini, tentu saja saya ingin ini diperiksa kebenarannya.

Saya juga sedikit menyesali pilihan karena solusinya sedikit lebih dalam daripada yang biasanya cocok dengan format tag. Setelah mengatakan ini, untuk mengikuti aturan situs, ada versi "golf" dari solusi saya di bagian bawah posting ini.


Kode

### 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;

Penjelasan:

Untuk memulai, kami menghitung semua 100 residu untuk kedua input. Kami melakukan ini dengan modulusfungsi dalam kode. Saya mencoba berhati-hati agar kami menggunakan modfungsi bawaan setelah setiap langkah. Ini memastikan bahwa kita tidak pernah memiliki angka yang lebih besar dari 540^2, yaitu 1 kurang dari kuadrat ke-100.

Setelah kita memiliki semua residu, kita dapat melakukan operasi yang diberikan dan modsetiap entri lagi. Sekarang kita memiliki designator yang unik untuk hasilnya, tetapi kita perlu menentukan jumlah minimal entri yang harus kita gunakan untuk mewakili hasil dan masing-masing input.

Mencari tahu berapa banyak residu yang sebenarnya kita butuhkan adalah bagian tersulit dari masalah ini. Untuk menentukan ini, kami melakukan sebagian besar langkah Teorema Sisa Cina (CRT). Namun, kami jelas harus melakukan modifikasi agar tidak berakhir dengan angka yang terlalu besar.

Biarkan prod(i)menjadi jumlah i-1bilangan prima pertama . Sebagai contoh,

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

Membiarkan Xmenjadi bilangan bulat. Membiarkan {r_i}menjadi residu X, yaitu

r_i = X mod p_i

Di mana p_iadalah iperdana th. Ini untuk 1<i<=100kasus kami.

Sekarang kita akan menggunakan CRT untuk menemukan urutan {u_i}sehingga jumlah idari prod(i) * u_isama dengan X. Perhatikan bahwa masing-masing u_isecara teknis juga merupakan residu, seperti u_i < p_i. Selanjutnya, jika X < prod(i)demikian u_i = 0. Ini sangat penting. Ini berarti bahwa dengan memeriksa nol jejak, kita dapat menentukan berapa banyak residu yang r_isebenarnya perlu kita wakili Xdalam RNS.

Jika Anda ingin memeriksa beberapa urutan u_i, partial_chinesefungsi mengembalikan u_iurutan.

Dengan bermain-main dengan CRT, saya dapat menemukan formula rekursif untuk u_inilai - nilai, memecahkan masalah menentukan berapa banyak residu yang kita butuhkan.

Rumusnya adalah:

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

Di mana SUMjumlah j in [1,i)dari u_j * prod(i).

Tentu saja, prod(i)dalam beberapa kasus tidak dapat benar-benar dihitung karena terlalu besar. Untuk tujuan ini, saya menggunakan phi_ifungsinya. Fungsi ini kembali prod(j) (mod p_i). Itu ada moddi setiap langkah, jadi kami tidak pernah benar-benar menghitung apa pun yang terlalu besar.

Jika Anda penasaran dari mana rumus ini berasal, saya akan merekomendasikan bekerja beberapa contoh CRT, yang dapat ditemukan di halaman wikipedia .

Akhirnya, untuk setiap input serta output kami, kami menghitung u_iurutan dan kemudian menentukan nol yang tertinggal. Kemudian kami membuang banyak r_idari ujung urutan residu.


"Golfed" Code, 2621 bytes

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;
Liam
sumber
Saya bingung karena RNS biasa tidak mengubah dimensi sesuai kebutuhan, tetapi tidakkah Anda membengkokkan aturan dengan menghitung nomor residu 100 yang diperpanjang dari input, daripada hanya dimensi yang dibutuhkan dan kemudian melakukan operasi? "Pertama, ubah setiap angka menjadi representasi RNS seperti dijelaskan di atas " bagi saya berarti nomor 'RNS' hanya memiliki residu yang diperlukan sebelum sesuatu dilakukan.
Linus
Maaf @Linus, saya pikir saya sudah menanggapi ini. Saya setuju dengan Anda, tetapi saya pikir perubahan yang diperlukan (yang akan saya buat) relatif sepele. Seperti yang saya lihat, yang harus saya lakukan adalah menghitung panjang residu input sebelum melakukan operasi. Menggunakan semua 100 bilangan prima untuk ketiga angka hanya memanfaatkan fakta bahwa semua angka dibatasi di atas
Liam
@Linus dan untuk menjawab pertanyaan pertama Anda, biasanya semua angka akan menggunakan jumlah residu yang sama. Itu akan membuat pertanyaan lebih sederhana
Liam
2

Mathematica, bukan golf

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}]
  • Fungsi rns[d_,l_]mengubah bilangan bulat basis-10 dmenjadi bilangan bulat RNS l.

  • Fungsi plus/ times/ subtracttambahkan / gandakan / kurangi satu bilangan bulat RNS ke / dari yang lain, yang keduanya memiliki panjang yang sama.

  • Fungsi mag[f_]mengestimasi besarnya perkiraan angka floating point fdalam hal batas bawah dari panjang representasi RNS-nya.

  • Fungsi ext[m_,n_,i_]menemukan sisa dari pembagian produk oleh mdan .Prime[Range@n]Prime[i]

  • Function multi[e_,p_,t_]memberikan multiplier terkecil yang mmemuaskan ituDivisible[m*e+t,p]

  • Fungsi appx[d_]mengambil 6digit pertama dari bilangan bulat desimal dan memberikan nilai titik apung perkiraannya.


Dengan bantuan fungsi-fungsi di atas, sekarang kami dapat memecahkan masalah yang rumit - untuk menentukan panjang hasilnya .

Pertama, saya harus mengklarifikasi bahwa itu bukan tugas yang mudah untuk menentukan panjang RNS integer. Untuk bilangan bulat kecil, kita dapat membandingkannya langsung dengan produk bilangan prima. Tetapi untuk bilangan bulat yang sangat besar, karena dilarang untuk menghitung produk bilangan prima yang sangat akurat, perbandingan seperti itu tidak lagi berfungsi.

Sebagai contoh, mengingat bahwa produk perdana 1untuk 30adalah 3.16*10^46, panjang RNS bilangan bulat sekitar 3.16*10^46mungkin menjadi 29atau 30. Fungsi magakan memberikan 29sebagai referensi untuk bilangan bulat ini, menunjukkan bahwa keduanya 29dan 30mungkin.

Setelah mengetahui besarnya, kami hanya mewakili bilangan bulat sesuai dengan besarnya itu secara langsung, berharap untuk menghitung panjang sebenarnya. Kuncinya di sini adalah menambahkan beberapa nomor baru ke nomor asli dan memodifikasi representasi RNS-nya, sampai representasi itu nol.

Misalnya, mag[211.]adalah 4, dan panjang 4representasi adalah {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

Dengan menambahkan beberapa angka, kami menambah 211ke angka terkecil yang dapat dibagi oleh 210( 2*3*5*7). Dan sekarang kita menyimpulkan bahwa angka aslinya lebih besar dari 210, karena 420sama dengan "kira-kira" dua kali lipat 210. Tidak sulit membayangkan bahwa jika kita mulai dari 209, angka terakhir adalah "kira-kira" 210.

Fungsi length[f_,n_]mengambil nilai titik apung funtuk memperkirakan besarnya, dan memperbaikinya berdasarkan representasi RNS-nya 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]]]]

Fungsi rnsOperation[a_,b_,op_,rnsop_]memberi rnsop[a,b]dan opsesuai dengan operasi normal yang digunakan untuk mendapatkan hasil perkiraan berdasarkan nilai floating point.

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]]]

Contoh

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} *)
njpipeorgan
sumber
1
Sayangnya, aturan yang diuraikan di pusat bantuan kami mengharuskan semua pengiriman menjadi pesaing serius untuk kriteria pemenang yang digunakan. Untuk kontes golf kode, ini berarti semua kiriman harus dip Golf.
Dennis
@ Dennis Saya tahu tentang aturan ini. Namun, bahkan tanpa bermain golf, saya pikir masalah ini cukup sulit dan kompleks, sehingga menyelesaikan masalah ini daripada bermain golf adalah tujuan saya.
njpipeorgan
ini mungkin tidak golf tetapi sangat singkat dibandingkan dengan program java saya: P, meskipun program saya mungkin jauh lebih cepat.
HopefullyHelpful
1
Saya merasa Anda mampu bermain golf ini
Rohan Jhunjhunwala
2

Python 3 , 435 byte

Tantangan ini telah ada dalam daftar ember saya untuk sementara waktu, tetapi baru-baru ini bahwa: a) Saya meluangkan waktu dan perhatian untuk mencoba jawaban; dan b) benar-benar menguji ide saya untuk menghitung ukuran angka (dan juga jumlah bilangan prima dengan membandingkannya dengan ukuran primorial) menggunakan beberapa kombinasi logaritma dan Teorem Sisa Tiongkok yang tidak suci. Sayangnya, bekerja dengan logaritma saat mencoba menentukan jumlah bilangan prima yang, misalnya, large_primorial + 3mengharuskan, berarti saya harus menemukan cara untuk mengatasi masalah floating-point.

Jadi, ini adalah port jawaban Liam .

Cobalah online!

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)]))

Penjelasan

Ketika mencoba menjawab jawaban Liam, saya pribadi menemukan beberapa penjelasan yang diberikan membingungkan, jadi ini adalah upaya saya untuk menjelaskan algoritmanya.

Pertama, kita mendapatkan residu ndan m.

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

Ini melibatkan mengubah semua digit dari string input dan mengubahnya menjadi angka-angka modulo masing-masing bilangan prima kita, misalnya untuk 28, kita akan memiliki [(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))

Kemudian kita tambahkan, gandakan, atau kurangi residu menggunakan pairwise eval()

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

Kemudian kita mendapatkan ukuran residu kita, yaitu jumlah minimum bilangan prima yang kita butuhkan.

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

Mendapatkan ukuran adalah bagian tersulit dan paling intensif kode. Kami menggunakan partial_chinesefungsi, yang memberi kami urutan u_iuntuk menentukan ukuran. Lebih u_idalam sebentar.

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

Urutan u_idihitung dengan mengambil setiap residu r_i, mengurangi jumlah u_j * primorial(j) for j in [1, i), dan kemudian dividingdengan primorial(i), semua modulo primes[i]. Yaitu u_i = (r_i - SUM) / primorial(i),. Lebih lanjut tentang fungsi primorial dan divisi kami sebentar lagi.

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)menghitung primorial(j) mod primes[i]. Divisi modulo setiap prime pmudah diimplementasikan dengan memeriksa invers perkalian secara manual, seperti yang kita dapat yakin bahwa apapun mungkin u_iadalah 0 <= u_i < pdijamin akan coprime untuk p dan dijamin invers perkalian.

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

Dengan semua yang dilakukan, kami mencetak string kami dan kami selesai.

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

Apa berikutnya

Ini menyenangkan untuk diterapkan. Saya masih ingin melihat apakah saya bisa menggunakan logaritma dalam beberapa cara di jawaban lain. Dan saya ingin menerapkan kode ini atau sesuatu seperti dalam bahasa golf fungsional, seperti APL atau Jelly. Semua dan semua saran dan koreksi golf dipersilahkan!

Sherlock9
sumber