Sistema di numero residuo


26

Sulla scia di grandi sfide, ho pensato che questo potesse essere interessante.

In questa sfida, utilizzeremo il Residue Number System (RNS) per eseguire addizioni, sottrazioni e moltiplicazioni su numeri interi di grandi dimensioni.

Cos'è l'RNS

L'RNS è uno dei molti modi in cui le persone hanno sviluppato per identificare numeri interi. In questo sistema, i numeri sono rappresentati da una sequenza di residui (che sono i risultati dopo un'operazione di modulo (cioè il resto dopo la divisione di numeri interi)). In questo sistema, ogni numero intero ha molte rappresentazioni. Per semplificare le cose, limiteremo le cose in modo che ogni numero intero sia rappresentato in modo univoco. Penso che sia più facile descrivere ciò che sta accadendo con un esempio concreto.

Esaminiamo i primi tre numeri primi: 2, 3, 5. Nel sistema RNS, possiamo usare questi tre numeri per rappresentare in modo univoco qualsiasi numero inferiore a 2 * 3 * 5 = 30 usando i residui. Prendi 21:

21 è inferiore a 30, quindi possiamo rappresentarlo utilizzando i risultati dopo la modifica per 2, 3 e 5. (ovvero il resto dopo la divisione di numeri interi per 2, 3 e 5)

Vorremmo identificare 21 con la seguente sequenza di numeri interi:

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

E così nel nostro sistema RNS, invece di "21", useremmo {1,0,1}.

In generale, dato un numero intero n , rappresentiamo n come { n mod 2, ..., n mod p_k } dove p_k è il primo più piccolo in modo che n sia inferiore al prodotto di tutti i numeri primi minore o uguale a p_k .

Un altro esempio, diciamo che abbiamo 3412. Qui dobbiamo usare 2,3,5,7,11,13 perché 2*3*5*7*11*13=30030considerando 2*3*5*7*11=2310che è troppo piccolo.

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

Notate che usando questo sistema possiamo rappresentare numeri molto grandi relativamente indolore. Usando i residui di {1, 2, 3, 4, 5, 6, 7, 8, ...}, possiamo rappresentare numeri fino a {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} rispettivamente. ( Ecco la serie )

Il nostro compito

Utilizzeremo questi residui per eseguire +, - e * su grandi numeri. Descriverò questi processi di seguito. Per ora ecco le specifiche di input e output.

Ingresso

Ti verranno dati due numeri (potenzialmente molto grandi) tramite uno stdin o un argomento di funzione. Verranno dati come stringhe di 10 cifre di base.

Ai fini di delineare ulteriormente il problema, chiamiamo il primo input ne il secondo m. Supponiamo n> m> = 0 .

Ti verrà anche dato +o -o *per indicare l'operazione da eseguire.

Produzione

Lascia che x sia un numero intero. Useremo [ x ] per fare riferimento alla rappresentazione RNS descritta sopra di x .

Devi produrre [n] <operator> [m] = [result]

Come eseguire le operazioni in RNS

Queste operazioni sono relativamente semplici. Dati due numeri nella notazione RNS, per aggiungerli, sottrarli o moltiplicarli, eseguire semplicemente le operazioni indicate dal punto di vista componente e quindi prendere il modulo.

vale a dire

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

Si noti che se il numero di residui utilizzati per rappresentare due numeri diversi non è lo stesso, quando si eseguono operazioni, sarà necessario estendere il numero "più corto" in modo che abbia lo stesso numero di residui. Questo segue lo stesso processo. Vedi i casi di test per un esempio.

Lo stesso vale se il risultato richiede più residui di entrambi gli input. Quindi entrambi gli ingressi devono essere "estesi".

Dettagli importanti

  • Ci occuperemo di grandi numeri qui, ma non arbitrariamente di grandi dimensioni. Saremo responsabili per i numeri fino al prodotto dei primi 100 numeri primi (vedi sotto). A tal fine, ti vengono dati i primi 100 numeri primi gratuitamente (nessun costo in byte) . Puoi incollarli in un array chiamato po qualcosa di idiomatico nella tua lingua e quindi sottrarre il numero di byte utilizzati per avviare questo array dal totale finale. Questo ovviamente significa che possono essere hardcoded oppure è possibile utilizzare un built-in per generarli.

  • Se per qualcuno motivo questa è la rappresentazione intera predefinita utilizzata nella tua lingua. Questo va bene.

  • Non è possibile utilizzare alcun tipo di Arbitrary Precision Integer a meno che non sia l'impostazione predefinita della propria lingua. Se è il valore predefinito, non è possibile utilizzarlo per memorizzare numeri interi che in genere non si adattano a 64 bit.

  • Per essere chiari, ogni numero intero sarà sempre rappresentato con il minor numero possibile di residui. Questo vale sia per l'ingresso che per l'uscita.

  • Penso che le altre specifiche dovrebbero impedire questo, ma per essere ridondanti: potresti non eseguire l'operazione indicata sugli input e quindi cambiare tutto in RNS e quindi output. È necessario modificare gli input in RNS e quindi eseguire le operazioni per produrre l'output.

Casi test

  1. Ingresso:

n = 10
m = 4
+

Produzione:

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

Spiegazione:

Innanzitutto, cambia ogni numero nella sua rappresentazione RNS come descritto sopra:

10 ~ {0,1,0}e 4 ~ {0,1}. Si noti che quando vogliamo fare un'aggiunta basata sui componenti, questo 10ha più componenti di 4. Pertanto dobbiamo "estendere" il numero più breve. Quindi scriveremo brevemente 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Ora procediamo con l'aggiunta e poi prendiamo il modulo.

  1. Ingresso
n=28
m=18
+

Produzione:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Input (mi schiaccio il viso sulla tastiera)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Output (suddiviso in righe separate per leggibilità):

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

nrichiede 28 numeri primi. mrichiede 10. n*mrichiede 33.

  1. Ingresso
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Produzione:

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

nusa 100 numeri primi. mutilizza 70 numeri primi. n-musa 99 numeri primi.

ChineseRemLi ho controllati usando l' implementazione integrata del teorema del residuo cinese su GAP (che sostanzialmente prende i numeri RNS e li cambia in base a 10 numeri interi). Credo che siano corretti. Se qualcosa sembra sospetto, per favore fatemi sapere.


Per chi se ne frega, il prodotto dei primi 100 numeri primi è:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Questo numero è 1 più grande del numero massimo che possiamo rappresentare usando il sistema dato (e una limitazione di 100 primi).

In qualche modo correlato


Penso che eseguire l'operazione sia lungi dall'essere la parte più difficile, per la quale mi sento strano per questa sfida.
njpipeorgan,

@njpipeorgan Sono d'accordo, l'esecuzione dell'operazione è semplicemente (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))in ES6 per esempio. Penso che la parte più difficile sia probabilmente trovare il numero di numeri primi necessari per rappresentare il risultato senza usare l'aritmetica di precisione arbitraria, sebbene la successiva conversione in RNS non sia esattamente banale.
Neil,

Posso avere l'input come questo ( 1234,1234,+)?
clismique,

@derpfacePython sì, anche le funzioni sono accettabili
Liam,

"semplicemente esegui le operazioni indicate dal punto di vista dei componenti" - da dove provengono i componenti extra nell'output?
smls

Risposte:


6

GAP

Alcuni retroscena: ammetterò che quando ho creato questa domanda, tanti mesi fa, non avevo un metodo per risolvere la parte difficile di questa domanda: determinare il numero corretto di numeri primi da usare. Abbiamo un sacco di persone molto intelligenti su questo sito e mi aspettavo davvero che qualcuno potesse trovare un modo per farlo abbastanza rapidamente. Tuttavia, poiché ciò non è avvenuto, non ero nemmeno sicuro che fosse davvero possibile risolvere questo problema. Quindi, ho dovuto prendere il tempo per escogitare un metodo. Credo che ciò che ho fatto non rompa nessuna delle regole in questa sfida, ovviamente mi piacerebbe che questo fosse verificato.

Mi rammarico anche leggermente della scelta di perché le soluzioni sono leggermente più approfondite di quelle che di solito si adattano al formato del tag. Detto questo, per seguire le regole del sito, c'è una versione "golfizzata" della mia soluzione in fondo a questo post.


Codice

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

Spiegazione:

Per iniziare, calcoliamo tutti i 100 dei residui per entrambi gli input. Lo facciamo con la modulusfunzione nel codice. Ho cercato di fare attenzione in modo da utilizzare la modfunzione integrata dopo ogni passaggio. Questo assicura che non abbiamo mai un numero maggiore di 540^2, che è 1 in meno del centesimo primo quadrato.

Dopo che abbiamo tutti i residui, possiamo eseguire modnuovamente l'operazione indicata e ciascuna voce. Ora abbiamo un designatore unico per il risultato, ma dobbiamo determinare il numero minimo di voci che dobbiamo usare per rappresentare il risultato e ciascuno degli input.

Capire quanti residui abbiamo effettivamente bisogno è di gran lunga la parte più difficile di questo problema. Per determinare ciò, eseguiamo la maggior parte dei passaggi del Teorema del residuo cinese (CRT). Tuttavia, ovviamente dobbiamo apportare modifiche in modo da non finire con numeri troppo grandi.

Lascia che prod(i)sia la somma dei primi i-1numeri primi. Per esempio,

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

Sia Xun numero intero. Lascia che {r_i}siano i residui di X, cioè

r_i = X mod p_i

Dov'è p_iil iprimo. Questo è per 1<i<=100nel nostro caso.

Ora useremo il CRT per trovare una sequenza {u_i}tale che la somma idi prod(i) * u_isia uguale a X. Si noti che ognuno u_iè anche tecnicamente un residuo, come u_i < p_i. Inoltre, se X < prod(i)allora u_i = 0. Questo è di fondamentale importanza. Significa che esaminando gli zeri finali, possiamo determinare quanti dei residui r_idobbiamo effettivamente rappresentare Xnell'RNS.

Se ti interessa esaminare alcune sequenze di u_i, la partial_chinesefunzione restituisce la u_isequenza.

Facendo casino con il CRT, sono stato in grado di trovare una formula ricorsiva per i u_ivalori, risolvendo il problema di determinare quanti residui abbiamo bisogno.

La formula è:

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

Dov'è SUMla somma j in [1,i)di u_j * prod(i).

Naturalmente, prod(i)in alcuni casi non può essere effettivamente calcolato perché è troppo grande. A tale scopo, ho usato la phi_ifunzione. Questa funzione ritorna prod(j) (mod p_i). È modad ogni passo, quindi non calcoliamo mai nulla di troppo grande.

Se sei curioso di sapere da dove provenga questa formula, ti consiglio di lavorare un paio di esempi di CRT, che puoi trovare sulla pagina di Wikipedia .

Infine, per ogni input e per il nostro output, calcoliamo la u_isequenza e quindi determiniamo gli zeri finali. Quindi ne eliminiamo molti r_idalla fine delle sequenze di residui.


Codice "Golfed", 2621 byte

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;

Sono confuso perché il normale RNS non cambia le dimensioni secondo necessità, ma non pieghi le regole calcolando il numero di 100 residui esteso dall'input, piuttosto che solo le dimensioni necessarie e quindi eseguendo le operazioni? "Per prima cosa, cambia ogni numero nella sua rappresentazione RNS come descritto sopra " per me significa che il numero 'RNS' dovrebbe avere solo i residui necessari prima che qualcosa venga fatto.
Linus,

Scusa @Linus, pensavo di aver già risposto a questo. Sono d'accordo con te, ma penso che il cambiamento richiesto (che farò) sia relativamente banale. A mio avviso, tutto ciò che devo fare è calcolare le lunghezze residue degli ingressi prima di eseguire l'operazione. L'uso di tutti i 100 numeri primi per tutti e tre i numeri si limita a sfruttare il fatto che tutti i numeri sono limitati sopra
Liam

@Linus e in risposta alla tua prima domanda, normalmente tutti i numeri utilizzerebbero lo stesso numero di residui. Ciò renderebbe la domanda molto più semplice
Liam,

2

Mathematica, non 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}]
  • La funzione rns[d_,l_]converte un numero intero base-10 din un numero intero RNS di lunghezza l.

  • Funzione plus/ times/ subtractaggiungi / moltiplica / sottrai un numero intero RNS da / a un altro, entrambi della stessa lunghezza.

  • La funzione mag[f_]stima la grandezza approssimativa del numero fin virgola mobile in termini del limite inferiore della lunghezza della sua rappresentazione RNS.

  • La funzione ext[m_,n_,i_]scopre il resto dalla divisione del prodotto di me Prime[Range@n]di Prime[i].

  • La funzione multi[e_,p_,t_]fornisce il più piccolo moltiplicatore msoddisfacenteDivisible[m*e+t,p]

  • La funzione appx[d_]prende le prime 6cifre di un numero intero decimale e fornisce il valore approssimativo in virgola mobile.


Con l'aiuto delle funzioni sopra, ora siamo in grado di risolvere un problema complicato: determinare la lunghezza del risultato .

Innanzitutto, devo chiarire che non è un compito facile determinare la lunghezza RNS di un numero intero. Per numeri interi piccoli, possiamo confrontarli direttamente con il prodotto di numeri primi. Ma per numeri interi molto grandi, poiché è vietato calcolare il prodotto di numeri primi infinitamente accurati, tale confronto non funziona più.

Ad esempio, dato che il prodotto di prime 1a 30è 3.16*10^46, la lunghezza RNS degli interi intorno 3.16*10^46può essere 29o 30. La funzione magfornirà 29come riferimento per questi numeri interi, dimostrando che entrambi 29e 30sono possibili.

Una volta che conosciamo la magnitudine, rappresentiamo semplicemente l'intero in base a tale magnitudine direttamente, sperando di calcolare la sua vera lunghezza. Il trucco qui è aggiungere alcuni nuovi numeri al numero originale e modificarne la rappresentazione RNS, fino a quando la rappresentazione è completamente zero.

Ad esempio, mag[211.]è 4e la sua 4rappresentazione della lunghezza è {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

Aggiungendo un numero, aumentiamo 211al numero più piccolo che è divisibile per 210( 2*3*5*7). E ora concludiamo che il numero originale è maggiore di 210, poiché 420equivale a "circa" due volte di 210. Non è difficile immaginare che se partiamo da 209, il numero finale è "approssimativamente" 210.

La funzione length[f_,n_]prende il valore fin virgola mobile per stimare la grandezza e correggerla in base alla sua rappresentazione 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]]]]

La funzione rnsOperation[a_,b_,op_,rnsop_]fornisce rnsop[a,b]e opcorrisponde alle normali operazioni utilizzate per ottenere risultati approssimativi basati su valori in virgola mobile.

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

Esempio

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
Sfortunatamente, le regole delineate nel nostro centro assistenza richiedono che tutte le richieste siano un serio concorrente per i criteri vincenti in uso. Per una gara di golf di codice, ciò significa che tutte le proposte devono essere giocate a golf.
Dennis,

@Dennis So di questa regola. Tuttavia, anche senza golf, penso che questo problema sia abbastanza difficile e complesso, quindi il mio obiettivo è risolverlo piuttosto che giocare a golf.
njpipeorgan,

questo forse non è golf ma dannatamente breve rispetto al mio programma Java: P, anche se il mio programma è probabilmente molto più veloce.
Speriamo utile

1
Sento che sei in grado di giocare a golf
Rohan Jhunjhunwala,

2

Python 3 , 435 byte

Questa sfida è stata nella mia lista dei desideri per un po ', ma è solo di recente che: a) ho dedicato tempo e attenzione a tentare effettivamente una risposta; e b) effettivamente testato la mia idea per calcolare la dimensione dei numeri (e quindi il numero di numeri primi confrontandolo con la dimensione dei numeri primari) usando una combinazione scellerata di logaritmi e teorema del residuo cinese. Sfortunatamente, lavorare con i logaritmi mentre cercavo di determinare il numero di numeri primi che, ad esempio, large_primorial + 3richiede, significava che dovevo trovare modi per aggirare i problemi in virgola mobile.

E quindi, questa è una porta della risposta di Liam .

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

Spiegazione

Mentre cercavo di portare la risposta di Liam, ho trovato personalmente alcune delle spiegazioni fornite in modo confuso, quindi questo è il mio tentativo di spiegare il suo algoritmo.

Innanzitutto, otteniamo i residui di ne m.

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

Ciò comporta la trasformazione di tutte le cifre delle stringhe di input e la loro trasformazione in numeri per ciascuno dei nostri numeri primi, ad es. Per 28, avremmo [(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))

Quindi aggiungiamo, moltiplichiamo o sottraggiamo i residui in coppia usando eval()

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

Quindi otteniamo le dimensioni dei nostri residui, ovvero il numero minimo di numeri primi di cui abbiamo bisogno.

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

Ottenere le dimensioni è la parte più complicata e più dispendiosa in termini di codice. Usiamo la partial_chinesefunzione, che ci dà la nostra sequenza di u_ideterminare la dimensione con. Più avanti u_itra un momento.

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

La sequenza u_iviene calcolata prendendo ogni residuo r_i, sottraendo la somma u_j * primorial(j) for j in [1, i)e quindi dividingda primorial(i)tutto il modulo primes[i]. Cioè, u_i = (r_i - SUM) / primorial(i). Maggiori informazioni sulle nostre funzioni primitive e di divisione in un momento.

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)calcola primorial(j) mod primes[i]. La divisione modulo qualsiasi numero primo ppuò essere facilmente implementata verificando manualmente le inversioni moltiplicative, poiché possiamo essere certi che sia possibileu_i si 0 <= u_i < pè garantito per essere primi con p e così è garantito un inverso moltiplicativo.

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

Fatto tutto ciò, stampiamo la nostra stringa e abbiamo finito.

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

Qual'è il prossimo

È stato divertente da implementare. Voglio ancora vedere se posso usare i logaritmi in qualche modo in un'altra risposta. E mi piacerebbe implementare questo codice o qualcosa del genere in un linguaggio di golf funzionale, come APL o Jelly. Tutti i suggerimenti e le correzioni sul golf sono i benvenuti!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.