In the vein of large number challenges I thought this one might be interesting.
In this challenge, we will be using the Residue Number System (RNS) to perform addition, subtraction, and multiplication on large integers.
What is the RNS
The RNS is one of many ways that people have developed to identify integers. In this system, numbers are represented by a sequence of residues (which are the results after a modulus operation (i.e. the remainder after integer division)). In this system, each integer has many representations. To keep things simple, we are going to limit things so that each integer is uniquely represented. I think it is easier to describe what is happening with a concrete example.
Let us look at the first three prime numbers: 2, 3, 5. In the RNS system, we can use these three numbers to uniquely represent any number that is less than 2*3*5 = 30 using residues. Take 21:
21 is less than 30, so we can represent it using the results after modding by 2, 3, and 5. (i.e. the remainder after integer dividing by 2, 3, and 5)
We would identify 21 with the following sequence of integers:
21 ~ { 21 mod 2, 21 mod 3, 21 mod 5 } = {1, 0, 1}
And so in our RNS system, instead of "21", we would use {1,0,1}.
In general given an integer n, we represent n as { n mod 2, ..., n mod p_k } where p_k is the smallest prime such that n is less than the product of all primes less than or equal to p_k.
Another example, say we have 3412. We need to use 2,3,5,7,11,13 here because 2*3*5*7*11*13=30030
whereas, 2*3*5*7*11=2310
which is too small.
3412 ~ { 3412 mod 2, 3412 mod 3, 3412, mod 5, ... , 3412 mod 13 } = { 0, 1, 2, 3, 2, 6 }
You notice that using this system we can represent very large numbers relatively painlessly. Using {1, 2, 3, 4, 5, 6, 7, 8, ... } residues, we can represent numbers up to {2, 6, 30, 210, 2310, 30030, 510510, 9699690 ...} respectively. ( Here is the series )
Our task
We will be using these residues to perform +, -, and * on large numbers. I will describe these processes below. For now here are the input and output specs.
Input
You will be given two (potentially very large) numbers via a stdin or function argument. They will be given as strings of base 10 digits.
For the purposes of outlining the problem further, we call the first input n
and the second m
. Assume n > m >= 0.
You will also be given +
or -
or *
to indicate the operation to perform.
Output
Let x be an integer. We will use [x] to refer to the RNS representation described above of x.
You are to output [n] <operator> [m] = [result]
How to perform the operations in RNS
These operations are relatively simple. Given two numbers in RNS notation, to add, subtract, or multiply them, simply perform the given operations component-wise and then take the modulus.
i.e.
{ 1, 2, 3 } + { 1, 1, 4 } = { (1+1) mod 2, (2+1) mod 3, (3+4) mod 5 } = {0, 0, 2}
Note that if the number of residues used to represent two different numbers are not the same, when performing operations, you will need to extend the "shorter" number so that it has the same number of residues. This follows the same process. See the test cases for an example.
The same goes if the result requires more residues than either input. Then both inputs need to be "extended".
Important Details
We will be dealing with big numbers here, but not arbitrarily large. We will be responsible for numbers up to the product of the first 100 primes (see below). To this end, you are given the first 100 primes for free (no byte cost). You may stick them in an array called
p
or something idiomatic to your language and then subtract the number of bytes used to initiate this array from your final total. This of course means that they may be hard-coded or you may use a built-in to generate them.If for someone reason this is the default integer representation used in your language. That is fine.
You may not use any Arbitrary Precision Integer type unless it is the default of your language. If it is the default, you may not use it to store integers that would not typically fit in 64 bits.
To be clear, each integer will always be represented with the fewest residues possible. This goes for both input and output.
I think the other specs should prevent this, but to be redundant: you may not perform the given operation on the inputs and then and then change everything to RNS and then output. You must change the inputs to RNS and then perform the operations to produce the output.
Test Cases
Input:
n = 10
m = 4
+
Output:
{ 0, 1, 0 } + { 0, 1 } = { 0, 2, 4 }
Explanation:
First, change each number to its RNS representation as described above:
10 ~ {0,1,0}
and 4 ~ {0,1}
. Notice that when we want to do component-wise addition, that 10
has more components than 4
. Therefore we must "extend" the shorter number. So we will briefly write 4 ~ {0,1} --> {0,1, 4 mod 5} = {0,1,4}
. Now we proceed with addition and then take the modulus.
- Input
n=28
m=18
+
Output:
[ 0, 1, 3 ] + [0, 0, 3 ] = [ 0, 1, 1, 4 ]
- Input (me mashing my face on the keyboard)
n=1231725471982371298419823012819231982571923
m=1288488183
*
Output (broken onto separate lines for readability):
[1, 2, 3, 6, 2, 10, 2, 1, 12, 16, 7, 15, 34, 29, 31, 5, 55, 32, 66, 61, 3, 76, 52, 14, 65, 44, 99, 57 ]
*
[1, 0, 3, 3, 4, 8, 9, 10, 8, 0 ]
=
[1, 0, 4, 4, 8, 2, 1, 10, 4, 0, 17, 7, 27, 21, 44, 51, 56, 9, 6, 9, 12, 0, 52, 36, 43, 68, 99, 24, 96, 39, 96, 66, 125]
n
requires 28 primes. m
requires 10. n*m
requires 33.
- Input
n=8709668761379269784034173446876636639594408083936553641753483991897255703964943107588335040121154680170867105541177741204814011615930342030904704147856733048115934632145172739949220591246493529224396454328521288726490
m=1699412683745170450115957274739962577420086093042490863793456500767137147999161679589295549397604032154933975242548831536518655879433595016
-
Output:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 509]
-
[0, 2, 1, 6, 1, 12, 11, 18, 14, 28, 21, 36, 37, 42, 16, 52, 41, 60, 16, 70, 49, 78, 80, 88, 49, 100, 13, 106, 4, 112, 68, 130, 36, 138, 37, 150, 0, 162, 8, 172, 163, 180, 18, 192, 129, 198, 135, 222, 78, 228, 90, 238, 57, 250, 36, 262, 87, 270, 206, 280, 193, 292, 253, 310, 224, 316, 57, 336, 48, 348]
=
[0, 1, 4, 1, 10, 1, 6, 1, 9, 1, 10, 1, 4, 1, 31, 1, 18, 1, 51, 1, 24, 1, 3, 1, 48, 1, 90, 1, 105, 1, 59, 1, 101, 1, 112, 1, 0, 1, 159, 1, 16, 1, 173, 1, 68, 1, 76, 1, 149, 1, 143, 1, 184, 1, 221, 1, 182, 1, 71, 1, 90, 1, 54, 1, 89, 1, 274, 1, 299, 1, 266, 1, 228, 1, 340, 1, 170, 1, 107, 1, 340, 1, 88, 1, 157, 1, 143, 1, 22, 1, 22, 1, 58, 1, 296, 1, 371, 1, 140]
n
uses 100 primes. m
uses 70 primes. n-m
uses 99 primes.
I checked these using the ChineseRem
built-in implementation of the Chinese Remainder theorem on GAP (which basically takes RNS numbers and changes them to base 10 integers). I believe they are correct. If something seems fishy, please let me know.
For those who care, the product of the first 100 primes is:
471193079990618495316248783476026042202057477340967552018863483961641533584503
422120528925670554468197243910409777715799180438028421831503871944494399049257
9030720635990538452312528339864352999310398481791730017201031090
This number is 1 larger than the maximal number we can represent using the given system (and 100 prime limitation).
(a,b,o)=>a.map((v,i)=>eval(v+o+b[i]))
in ES6 for instance. I think the hardest part is probably finding the number of primes needed to represent the result without using arbitrary precision arithmetic, although the subsequent conversion to RNS isn't exactly trivial. \$\endgroup\$1234,1234,+
)? \$\endgroup\$