Système de numération des résidus


26

Dans la foulée de nombreux défis, j'ai pensé que celui-ci pouvait être intéressant.

Dans ce défi, nous utiliserons le système de numération des résidus (RNS) pour effectuer l'addition, la soustraction et la multiplication sur de grands nombres entiers.

Qu'est-ce que le RNS

Le RNS est l'une des nombreuses façons que les gens ont développées pour identifier les nombres entiers. Dans ce système, les nombres sont représentés par une séquence de résidus (qui sont les résultats après une opération de module (c'est-à-dire le reste après la division entière)). Dans ce système, chaque entier a de nombreuses représentations. Pour garder les choses simples, nous allons limiter les choses afin que chaque entier soit représenté de manière unique. Je pense qu'il est plus facile de décrire ce qui se passe avec un exemple concret.

Examinons les trois premiers nombres premiers: 2, 3, 5. Dans le système RNS, nous pouvons utiliser ces trois nombres pour représenter de manière unique tout nombre inférieur à 2 * 3 * 5 = 30 en utilisant des résidus. Prenez 21:

21 est inférieur à 30, nous pouvons donc le représenter en utilisant les résultats après modding par 2, 3 et 5. (c'est-à-dire le reste après la division de l'entier par 2, 3 et 5)

Nous identifierions 21 avec la séquence d'entiers suivante:

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

Et donc dans notre système RNS, au lieu de "21", nous utiliserions {1,0,1}.

En général, étant donné un entier n , nous représentons n comme { n mod 2, ..., n mod p_k } où p_k est le plus petit premier tel que n est inférieur au produit de tous les nombres premiers inférieurs ou égaux à p_k .

Un autre exemple, disons que nous avons 3412. Nous devons utiliser 2,3,5,7,11,13 ici parce 2*3*5*7*11*13=30030que, 2*3*5*7*11=2310ce qui est trop petit.

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

Vous remarquez qu'en utilisant ce système, nous pouvons représenter de très grands nombres relativement facilement. En utilisant {1, 2, 3, 4, 5, 6, 7, 8, ...} résidus, nous pouvons représenter des nombres jusqu'à {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} respectivement. ( Voici la série )

Notre mission

Nous utiliserons ces résidus pour effectuer +, - et * sur de grands nombres. Je décrirai ces processus ci-dessous. Pour l'instant, voici les spécifications d'entrée et de sortie.

Contribution

Vous recevrez deux nombres (potentiellement très grands) via un argument stdin ou fonction. Ils seront donnés sous forme de chaînes de base 10 chiffres.

Afin de décrire le problème plus en détail, nous appelons la première entrée net la seconde m. Supposons que n> m> = 0 .

Vous recevrez également +ou -ou *pour indiquer l'opération à effectuer.

Sortie

Soit x un entier. Nous utiliserons [ x ] pour faire référence à la représentation RNS décrite ci-dessus de x .

Vous devez sortir [n] <operator> [m] = [result]

Comment effectuer les opérations dans RNS

Ces opérations sont relativement simples. Étant donné deux nombres en notation RNS, pour les ajouter, les soustraire ou les multiplier, effectuez simplement les opérations données par composant, puis prenez le module.

c'est à dire

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

Notez que si le nombre de résidus utilisé pour représenter deux nombres différents n'est pas le même, lors de l'exécution des opérations, vous devrez étendre le nombre "plus court" afin qu'il ait le même nombre de résidus. Cela suit le même processus. Voir les cas de test pour un exemple.

Il en va de même si le résultat nécessite plus de résidus que l'une ou l'autre entrée. Ensuite, les deux entrées doivent être "étendues".

Détails importants

  • Nous allons traiter ici de grands nombres, mais pas arbitrairement grands. Nous serons responsables des nombres jusqu'au produit des 100 premiers nombres premiers (voir ci-dessous). À cette fin, vous recevez gratuitement les 100 premiers nombres premiers (sans coût en octets) . Vous pouvez les coller dans un tableau appelé pou quelque chose d'idiomatique dans votre langue, puis soustraire le nombre d'octets utilisés pour lancer ce tableau de votre total final. Cela signifie bien sûr qu'ils peuvent être codés en dur ou que vous pouvez utiliser un intégré pour les générer.

  • Si pour une raison quelconque, c'est la représentation entière par défaut utilisée dans votre langue. C'est bon.

  • Vous ne pouvez pas utiliser de type entier de précision arbitraire, sauf s'il s'agit de la langue par défaut de votre langue. S'il s'agit de la valeur par défaut, vous ne pouvez pas l'utiliser pour stocker des entiers qui ne tiennent généralement pas en 64 bits.

  • Pour être clair, chaque entier sera toujours représenté avec le moins de résidus possible. Cela vaut pour l'entrée et la sortie.

  • Je pense que les autres spécifications devraient empêcher cela, mais pour être redondantes: vous ne pouvez pas effectuer l'opération donnée sur les entrées, puis tout changer en RNS puis en sortie. Vous devez remplacer les entrées par RNS, puis effectuer les opérations pour produire la sortie.

Cas de test

  1. Contribution:

n = 10
m = 4
+

Sortie:

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

Explication:

Tout d'abord, changez chaque nombre en sa représentation RNS comme décrit ci-dessus:

10 ~ {0,1,0}et 4 ~ {0,1}. Notez que lorsque nous voulons effectuer une addition par composant, cela 10a plus de composants que 4. Par conséquent, nous devons "étendre" le nombre le plus court. Nous allons donc écrire brièvement 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}. Maintenant, nous procédons à l'addition, puis prenons le module.

  1. Contribution
n=28
m=18
+

Sortie:

 [ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
  1. Entrée (moi écrasant mon visage sur le clavier)
n=1231725471982371298419823012819231982571923
m=1288488183
*

Sortie (divisée sur des lignes distinctes pour plus de lisibilité):

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

nnécessite 28 nombres premiers. mnécessite 10. n*mnécessite 33.

  1. Contribution
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-

Sortie:

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

nutilise 100 nombres premiers. mutilise 70 nombres premiers. n-mutilise 99 nombres premiers.

J'ai vérifié ceux-ci en utilisant l' ChineseRemimplémentation intégrée du théorème du reste chinois sur GAP (qui prend essentiellement des nombres RNS et les change en base 10 entiers). Je pense qu'ils ont raison. Si quelque chose semble louche, faites-le moi savoir.


Pour ceux qui s'en soucient, le produit des 100 premiers nombres premiers est:

471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090

Ce nombre est 1 plus grand que le nombre maximal que nous pouvons représenter en utilisant le système donné (et 100 limitation principale).

Assez lié


Je pense que l'exécution de l'opération est loin d'être la partie la plus difficile, pour laquelle je me sens étrange face à ce défi.
njpipeorgan

@njpipeorgan Je suis d'accord, l'exécution de l'opération se fait simplement (a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))en ES6 par exemple. Je pense que la partie la plus difficile est probablement de trouver le nombre de nombres premiers nécessaires pour représenter le résultat sans utiliser d'arithmétique de précision arbitraire, bien que la conversion ultérieure en RNS ne soit pas exactement triviale.
Neil

Puis-je avoir l'entrée comme celle-ci ( 1234,1234,+)?
clismique

@derpfacePython oui les fonctions sont également acceptables
Liam

"exécutez simplement les opérations données au niveau des composants" - d'où viennent les composants supplémentaires dans la sortie?
smls

Réponses:


6

ÉCART

Contexte: Je dois admettre que lorsque j'ai créé cette question, il y a tant de mois, je n'avais pas de méthode pour résoudre la partie difficile de cette question: déterminer le nombre correct de nombres premiers à utiliser. Nous avons beaucoup de gens très intelligents sur ce site, et je m'attendais vraiment à ce que quelqu'un trouve un moyen de le faire assez rapidement. Cependant, comme cela ne s'est pas produit, je n'étais même pas sûr qu'il était vraiment possible de résoudre ce problème. J'ai donc dû prendre le temps de concevoir une méthode. Je crois que ce que j'ai fait ne viole aucune des règles de ce défi, bien sûr, j'aimerais que cela soit vérifié.

Je regrette également légèrement le choix du car les solutions sont un peu plus en profondeur que celles qui conviendraient habituellement au format tag. Cela dit, pour suivre les règles du site, il y a une version "golfée" de ma solution au bas de cet article.


Code

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

Explication:

Pour commencer, nous calculons la totalité des 100 résidus pour les deux entrées. Nous le faisons avec la modulusfonction dans le code. J'ai essayé d'être prudent afin que nous utilisions la modfonction intégrée après chaque étape. Cela garantit que nous n'avons jamais un nombre supérieur à 540^2, qui est inférieur de 1 au 100e premier carré.

Une fois que nous avons tous les résidus, nous pouvons effectuer l'opération donnée et modchaque entrée à nouveau. Nous avons maintenant un désignateur unique pour le résultat, mais nous devons déterminer le nombre minimal d'entrées que nous devons utiliser pour représenter le résultat et chacune des entrées.

Déterminer le nombre de résidus dont nous avons réellement besoin est de loin la partie la plus difficile de ce problème. Pour déterminer cela, nous effectuons la plupart des étapes du théorème du reste chinois (CRT). Cependant, nous devons évidemment apporter des modifications afin de ne pas nous retrouver avec des nombres trop grands.

Soit prod(i)la somme des premiers i-1nombres premiers. Par exemple,

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

Soit Xun entier. Soit {r_i}les résidus de X, c'est-à-dire

r_i = X mod p_i

p_iest le ie premier. C'est pour 1<i<=100dans notre cas.

Maintenant, nous allons utiliser le CRT pour trouver une séquence {u_i}telle que la somme ide prod(i) * u_isoit égale à X. Notez que chacun u_iest également techniquement un résidu, comme u_i < p_i. De plus, si X < prod(i)alors u_i = 0. Ceci est d'une importance cruciale. Cela signifie qu'en examinant les zéros à la fin, nous pouvons déterminer le nombre de résidus que r_inous devons réellement représenter Xdans le RNS.

Si vous souhaitez examiner certaines séquences de u_i, la partial_chinesefonction renvoie la u_iséquence.

En jouant avec le CRT, j'ai pu trouver une formule récursive pour les u_ivaleurs, résolvant le problème de déterminer combien de résidus nous avons besoin.

La formule est:

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

SUMest la somme j in [1,i)de u_j * prod(i).

Bien sûr, prod(i)ne peut pas être calculé dans certains cas car il est trop grand. Pour cela, j'ai utilisé la phi_ifonction. Cette fonction revient prod(j) (mod p_i). C'est modà chaque étape, donc nous ne calculons jamais rien de trop grand.

Si vous êtes curieux d'où vient cette formule, je recommanderais de travailler quelques exemples CRT, qui peuvent être trouvés sur la page wikipedia .

Enfin, pour chaque entrée ainsi que pour notre sortie, nous calculons la u_iséquence puis déterminons les zéros de fin. Ensuite, nous jetons ce nombre r_ià la fin des séquences de résidus.


Code "Golfé", 2621 octets

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;

Je suis confus parce que le RNS normal ne modifie pas les dimensions selon les besoins, mais ne pliez-vous pas les règles en calculant le nombre étendu de 100 résidus à partir de l'entrée, plutôt que seulement les dimensions nécessaires et en effectuant ensuite des opérations? "Tout d'abord, changez chaque numéro en sa représentation RNS comme décrit ci-dessus " pour moi signifie que le numéro 'RNS' ne devrait avoir que les résidus nécessaires avant que quoi que ce soit soit fait.
Linus

Désolé @Linus, je pensais avoir déjà répondu à cela. Je suis d'accord avec vous, mais je pense que le changement requis (que je ferai) est relativement trivial. Selon moi, tout ce que je dois faire est de calculer les longueurs résiduelles des entrées avant d'effectuer l'opération. L'utilisation des 100 nombres premiers pour les trois nombres ne fait que tirer parti du fait que tous les nombres sont bornés ci
Liam

@Linus et en réponse à votre première question, normalement tous les nombres utiliseraient le même nombre de résidus. Cela rendrait la question beaucoup plus simple
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 fonction rns[d_,l_]convertit un entier de base 10 den un entier RNS de longueur l.

  • Fonction plus/ times/ subtractajouter / multiplier / soustraire un entier RNS à / d'un autre, les deux étant de la même longueur.

  • La fonction mag[f_]estime l'amplitude approximative du nombre fà virgule flottante en fonction de la limite inférieure de la longueur de sa représentation RNS.

  • Une fonction ext[m_,n_,i_] découvre le reste de la division du produit de met Prime[Range@n]par Prime[i].

  • Une fonction multi[e_,p_,t_] donne le plus petit multiplicateur msatisfaisant queDivisible[m*e+t,p]

  • Une fonction appx[d_] prend les premiers 6chiffres d'un entier décimal et donne sa valeur approximative en virgule flottante.


Avec l'aide des fonctions ci-dessus, nous sommes maintenant en mesure de résoudre un problème délicat - pour déterminer la longueur du résultat .

Tout d'abord, je dois préciser qu'il n'est pas facile de déterminer la longueur RNS d'un entier. Pour les petits entiers, nous pouvons les comparer directement avec le produit des nombres premiers. Mais pour les très grands entiers, puisqu'il est interdit de calculer le produit de nombres premiers infiniment précis, une telle comparaison ne fonctionne plus.

Par exemple, étant donné que le produit de prime 1à 30est 3.16*10^46, la longueur RNS des entiers autour 3.16*10^46peut éventuellement être 29ou 30. La fonction magdonnera 29comme référence pour ces entiers, montrant que les deux 29et 30sont possibles.

Une fois la magnitude connue, nous représentons simplement l'entier en fonction de cette magnitude directement, en espérant calculer sa vraie longueur. L'astuce consiste à ajouter de nouveaux nombres au nombre d'origine et à modifier sa représentation RNS, jusqu'à ce que la représentation soit entièrement nulle.

Par exemple, mag[211.]est 4, et sa 4représentation de longueur est {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

En ajoutant un certain nombre, nous augmentons 211au plus petit nombre qui est divisible par 210( 2*3*5*7). Et maintenant, nous concluons que le nombre d'origine est supérieur à 210, car 420est égal à "environ" deux fois 210. Il n'est pas difficile d'imaginer que si nous partons de 209, le nombre final est "approximativement" 210.

La fonction length[f_,n_]prend la valeur fen virgule flottante pour estimer l'amplitude et la corriger en se basant sur sa représentation 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 fonction rnsOperation[a_,b_,op_,rnsop_]donne rnsop[a,b]et opcorrespond aux opérations normales utilisées pour obtenir des résultats approximatifs basés sur des valeurs à virgule flottante.

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

Exemple

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
Malheureusement, les règles décrites dans notre centre d'aide exigent que toutes les soumissions soient un candidat sérieux pour les critères gagnants utilisés. Pour un concours de golf à code, cela signifie que toutes les soumissions doivent être jouées au golf.
Dennis

@Dennis Je connais cette règle. Cependant, même sans le golf, je pense que ce problème est suffisamment difficile et complexe, de sorte que la résolution de ce problème plutôt que le golf est mon objectif.
njpipeorgan

ce n'est peut-être pas un golf mais sacrément court par rapport à mon programme java: P, bien que mon programme soit probablement beaucoup plus rapide.
Espérons que ce sera

1
J'ai l'impression que vous êtes capable de jouer au golf
Rohan Jhunjhunwala

2

Python 3 , 435 octets

Ce défi est sur ma liste de seaux depuis un certain temps, mais ce n'est que récemment que: a) j'ai consacré du temps et de l'attention à essayer de trouver une réponse; et b) a réellement testé mon idée pour calculer la taille des nombres (et donc le nombre de nombres premiers en la comparant à la taille des primitifs) en utilisant une combinaison impie de logarithmes et le théorème des restes chinois. Malheureusement, travailler avec des logarithmes tout en essayant de déterminer le nombre de nombres premiers qui, par exemple,large_primorial + 3 nécessite, signifiait que je devais trouver des moyens de contourner les problèmes à virgule flottante.

Et donc, c'est un port de la réponse de Liam .

Essayez-le en ligne!

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

Explication

En essayant de porter la réponse de Liam, j'ai personnellement trouvé que certaines des explications données étaient formulées de manière confuse, c'est donc ma tentative d'expliquer son algorithme.

Tout d'abord, nous obtenons les résidus de net m.

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

Cela implique de transformer tous les chiffres des chaînes d'entrée et de les transformer en nombres modulo chacun de nos nombres premiers, par exemple pour 28, nous aurions [(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))

Ensuite, nous ajoutons, multiplions ou soustrayons les résidus par paire en utilisant eval()

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

Ensuite, nous obtenons la taille de nos résidus, c'est-à-dire le nombre minimum de nombres premiers dont nous avons besoin.

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

Obtenir les tailles est la partie la plus délicate et la plus gourmande en code. Nous utilisons la partial_chinesefonction, qui nous donne notre séquence de u_ipour déterminer la taille avec. Plus d'informations u_idans un instant.

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 séquence u_iest calculée en prenant chaque résidu r_i, en soustrayant la somme u_j * primorial(j) for j in [1, i), puis dividingpar primorial(i)tout le modulo primes[i]. C'est u_i = (r_i - SUM) / primorial(i). Plus sur nos fonctions primitives et de division dans un instant.

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)calcule primorial(j) mod primes[i]. Division modulo tout premier pest facilement mis en œuvre en vérifiant manuellement multiplicatif inverses, comme on peut être sûr que toute possible u_iest 0 <= u_i < pest assuré d'être à la page et coprime donc est garanti un inverse multiplicatif.

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

Avec tout cela fait, nous imprimons notre chaîne et nous avons terminé.

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

Et après

C'était amusant à mettre en œuvre. Je veux toujours voir si je peux utiliser les logarithmes d'une manière ou d'une autre dans une autre réponse. Et j'aimerais implémenter ce code ou quelque chose comme dans un langage de golf fonctionnel, comme APL ou Jelly. Toutes les suggestions et corrections de golf sont les bienvenues!

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.