Comment générer un BigInteger aléatoire dans une certaine plage?

Considérez cette méthode qui fonctionne bien:

public static bool mightBePrime(int N) { BigInteger a = rGen.Next (1, N-1); return modExp (a, N - 1, N) == 1; } 

Maintenant, afin de satisfaire à une exigence de la classe que je suis en train de suivre, mightBePrime doit accepter un BigInteger N, mais cela signifie que j’ai besoin d’une méthode différente pour générer mon BigInteger aléatoire a.

Ma première idée était de faire quelque chose comme BigInteger a = (N-1) * rGen.NextDouble () , mais un BigInteger ne peut pas être multiplié par un double.

Comment générer un BigInteger aléatoire compris entre 1 et N-1 où N est un BigInteger?

Paul a suggéré dans un commentaire que je génère un nombre en utilisant des octets aléatoires, puis que je le jette s’il est trop gros. Voici ce que j’ai trouvé (réponse de Marcel + conseil de Paul):

 public static BigInteger RandomIntegerBelow(BigInteger N) { byte[] bytes = N.ToByteArray (); BigInteger R; do { random.NextBytes (bytes); bytes [bytes.Length - 1] &= (byte)0x7F; //force sign bit to positive R = new BigInteger (bytes); } while (R >= N); return R; } 

http://amirshenouda.wordpress.com/2012/06/29/implementing-rsa-c/ a également aidé un peu.

Utilisez la classe aléatoire

 public BigInteger getRandom(int length){ Random random = new Random(); byte[] data = new byte[length]; random.NextBytes(data); return new BigInteger(data); } 

L’implémentation naïve échouera en moyenne 64 fois avant de trouver un BigInteger valide dans la plage spécifiée.

Dans le pire des cas, mon implémentation ne réessayera en moyenne que 0,5 fois (comme suit: 50% des fois, elle trouvera un résultat du premier coup).

De plus, contrairement à l’arithmétique modulaire, mon implémentation maintient une dissortingbution uniforme .

Explication

Nous devons générer un BigInteger aléatoire entre min et max .

  1. Si min > max , nous échangeons min avec max
  2. Pour simplifier l’implémentation, nous passons de [min, max] à [0, max-min] . Ainsi, nous n’aurons pas à traiter le bit de signe.
  3. Nous comptons combien d’octets max contient ( bytes.Length )
  4. À partir du bit le plus significatif, nous comptons combien de bits sont 0 ( zeroBits )
  5. Nous générons une séquence aléatoire d’ bytes.Length octets
  6. Nous soaps que pour que notre séquence soit < max , au moins zeroBits bit du bit le plus significatif doit être 0, nous utilisons donc un zeroBitMask pour les définir avec une seule opération bit à bit & opération sur l' octet le plus significatif . gagner beaucoup de temps en réduisant le changement de génération d'un nombre hors de notre gamme
  7. Nous vérifions si le nombre que nous avons généré est > max , et si nous essayons à nouveau
  8. On décale la plage de [0, max-min] à [min, max] en ajoutant min à notre résultat

Et nous avons notre numéro. 😊

la mise en oeuvre

 public static BigInteger RandomInRange(RandomNumberGenerator rng, BigInteger min, BigInteger max) { if (min > max) { var buff = min; min = max; max = buff; } // offset to set min = 0 BigInteger offset = -min; min = 0; max += offset; var value = randomInRangeFromZeroToPositive(rng, max) - offset; return value; } private static BigInteger randomInRangeFromZeroToPositive(RandomNumberGenerator rng, BigInteger max) { BigInteger value; var bytes = max.ToByteArray(); // count how many bits of the most significant byte are 0 // NOTE: sign bit is always 0 because `max` must always be positive byte zeroBitsMask = 0b00000000; var mostSignificantByte = bytes[bytes.Length - 1]; // we try to set to 0 as many bits as there are in the most significant byte, starting from the left (most significant bits first) // NOTE: `i` starts from 7 because the sign bit is always 0 for (var i = 7; i >= 0; i--) { // we keep iterating until we find the most significant non-0 bit if ((mostSignificantByte & (0b1 << i)) != 0) { var zeroBits = 7 - i; zeroBitsMask = (byte)(0b11111111 >> zeroBits); break; } } do { rng.GetBytes(bytes); // set most significant bits to 0 (because `value > max` if any of these bits is 1) bytes[bytes.Length - 1] &= zeroBitsMask; value = new BigInteger(bytes); // `value > max` 50% of the times, in which case the fastest way to keep the dissortingbution uniform is to try again } while (value > max); return value; } 

Tester

 using (var rng = RandomNumberGenerator.Create()) { BigInteger min = 0; BigInteger max = 5; var attempts = 10000000; var count = new int[(int)max + 1]; var sw = Stopwatch.StartNew(); for (var i = 0; i < attempts; i++) { var v = BigIntegerUtils.RandomInRange(rng, min, max); count[(int)v]++; } var time = sw.Elapsed; Console.WriteLine("Generated {0} big integers from {1} to {2} in {3}", attempts, min, max, time); Console.WriteLine("On average: {0} ms/integer or {1} integers/second", time.TotalMilliseconds / attempts, attempts / time.TotalSeconds); for (var i = 0; i <= max; i++) Console.WriteLine("{0} generated {1}% of the times ({2} times)", i, count[i] * 100d / attempts, count[i]); } 

Test de la sortie sur mon i7-6500U:

 Generated 10000000 big integers from 0 to 5 in 00:00:09.5413677 On average: 0.00095413677 ms/integer or 1048067.77334449 integers/second 0 generated 16.66633% of the times (1666633 times) 1 generated 16.6717% of the times (1667170 times) 2 generated 16.66373% of the times (1666373 times) 3 generated 16.6666% of the times (1666660 times) 4 generated 16.68271% of the times (1668271 times) 5 generated 16.64893% of the times (1664893 times) 

Une autre sortie de test sur mon i7-6500U

 Generated 10000000 big integers from 0 to 10^100 in 00:00:17.5036570 On average: 0.0017503657 ms/integer or 571309.184132207 integers/second 

Créer un tableau d’octets et convertir en BigInteger:

 public BigInteger random_generate(BigInteger maxValue) { Random random = new Random(); byte[] maxValue_array = maxValue.ToByteArray(); byte[] randomValue_array = new byte[maxValue_array.Count()]; bool on_limit = true; //make sure randomValue won't greater than maxValue for (int generate_byte = maxValue_array.Count() - 1; generate_byte >= 0; generate_byte--) { byte random_byte = 0; if (on_limit) { random_byte = (byte)random.Next(maxValue_array[generate_byte]); if (random_byte != (byte)random.Next(maxValue_array[generate_byte])) { on_limit = false; } } else { random_byte = (byte)random.Next(256); } randomValue_array[generate_byte] = random_byte; } return new BigInteger(randomValue_array); } 

Si maxValue est trop petit, l’aléatoire générera la même valeur. Donc, vous pouvez définir le hasard en dehors de la fonction:

 static void Main(ssortingng[] args) { Random random = new Random(); BigInteger i = random_generate(10, random); //10 is just a example } public BigInteger random_generate(BigInteger maxValue, Random random) { byte[] maxValue_array = maxValue.ToByteArray(); //...rest of the code... } 

Voici un autre moyen de générer des nombres dans la plage sans perdre de valeurs et de permettre aux BigIntegers de min et max.

 public BigInteger RandomBigInteger(BigInteger min, BigInteger max) { Random rnd = new Random(); ssortingng numeratorSsortingng, denominatorSsortingng; double fraction = rnd.NextDouble(); BigInteger inRange; //Maintain all 17 digits of precision, //but remove the leading zero and the decimal point; numeratorSsortingng = fraction.ToSsortingng("G17").Remove(0, 2); //Use the length instead of 17 in case the random //fraction ends with one or more zeros denominatorSsortingng = ssortingng.Format("1E{0}", numeratorSsortingng.Length); inRange = (max - min) * BigInteger.Parse(numeratorSsortingng) / BigInteger.Parse(denominatorSsortingng, System.Globalization.NumberStyles.AllowExponent) + min; return inRange; } 

Pour la généralité, vous pouvez également spécifier la précision. Cela semble fonctionner.

  public BigInteger RandomBigIntegerInRange(BigInteger min, BigInteger max, int precision) { Random rnd = new Random(); ssortingng numeratorSsortingng, denominatorSsortingng; double fraction = rnd.NextDouble(); BigInteger inRange; numeratorSsortingng = GenerateNumeratorWithSpecifiedPrecision(precision); denominatorSsortingng = ssortingng.Format("1E{0}", numeratorSsortingng.Length); inRange = (max - min) * BigInteger.Parse(numeratorSsortingng) / BigInteger.Parse(denominatorSsortingng, System.Globalization.NumberStyles.AllowExponent) + min; return inRange; } private ssortingng GenerateNumeratorWithSpecifiedPrecision(int precision) { Random rnd = new Random(); ssortingng answer = ssortingng.Empty; while(answer.Length < precision) { answer += rnd.NextDouble().ToString("G17").Remove(0, 2); } if (answer.Length > precision) //Most likely { answer = answer.Subssortingng(0, precision); } return answer; } 

La méthode Range suivante renverra un IEnumerable dans la plage que vous spécifiez. Une méthode d’extension simple renverra un élément aléatoire dans IEnumerable.

 public static IEnumerable Range(BigInteger from, BigInteger to) { for(BigInteger i = from; i < to; i++) yield return i; } public static class Extensions { public static BigInteger RandomElement(this IEnumerable enumerable, Random rand) { int index = rand.Next(0, enumerable.Count()); return enumerable.ElementAt(index); } } 

usage:

 Random rnd = new Random(); var big = Range(new BigInteger(10000000000000000), new BigInteger(10000000000000020)).RandomElement(rnd); 

// retourne des valeurs aléatoires et dans ce cas, il était 10000000000000003