Comment générer une valeur BigInteger aléatoire en Java?
J'ai besoin de générer des entiers aléatoires arbitrairement grands dans la plage 0 (inclus) à n (exclusif). Ma pensée initiale était d'appeler nextDouble
et multipliez par n, mais une fois que n devient plus grand que 253, les résultats ne seraient plus uniformément répartis.
BigInteger
a la suite de constructeur disponible:
public BigInteger(int numBits, Random rnd)
Construit un BigInteger généré aléatoirement, uniformément réparti sur la plage 0 à (2numBits - 1), inclusif.
Comment cela peut-il être utilisé pour obtenir une valeur aléatoire dans la plage de 0 - n, où n n'est pas une puissance de 2?
7 réponses
Utiliser une boucle:
BigInteger r;
do {
r = new BigInteger(n.bitLength(), rnd);
} while (r.compareTo(n) >= 0);
En moyenne, cela nécessitera moins de deux itérations, et la sélection sera uniforme.
Edit: Si votre RNG est cher, vous pouvez limiter le nombre d'itérations de la façon suivante:
int nlen = n.bitLength();
BigInteger nm1 = n.subtract(BigInteger.ONE);
BigInteger r, s;
do {
s = new BigInteger(nlen + 100, rnd);
r = s.mod(n);
} while (s.subtract(r).add(nm1).bitLength() >= nlen + 100);
// result is in 'r'
Avec cette version, il est hautement improbable que la boucle soit prise plus d'une fois (moins d'une chance dans 2^100, c'est-à-dire beaucoup moins que la probabilité que la machine hôte s'enflamme spontanément dans la seconde suivante). D'un autre côté, l'opération mod()
est coûteuse en calcul, donc cette version est probablement plus lente que la précédente, sauf si l'instance rnd
est exceptionnellement lente.
La méthode suivante utilise le constructeur BigInteger(int numBits, Random rnd)
et rejette le résultat s'il est plus grand que le n spécifié.
public BigInteger nextRandomBigInteger(BigInteger n) {
Random rand = new Random();
BigInteger result = new BigInteger(n.bitLength(), rand);
while( result.compareTo(n) >= 0 ) {
result = new BigInteger(n.bitLength(), rand);
}
return result;
}
L'inconvénient c'est que le constructeur est appelé un nombre quelconque de fois, mais dans le pire des cas (n est que très légèrement supérieure à une puissance de 2) le nombre d'appels au constructeur devrait être seulement 2 fois.
L'approche la plus simple (de loin) serait d'utiliser le constructeur spécifié pour générer un nombre aléatoire avec le bon nombre de bits (floor(log2 n) + 1
), puis de le jeter s'il est supérieur à N. Dans le pire des cas possible (par exemple un nombre dans la plage [0, 2n + 1) vous jetterez un peu moins de la moitié des valeurs que vous créez, en moyenne.
Pourquoi ne pas construire un BigInteger aléatoire, puis en construire un BigDecimal ?
Il y a un constructeur dans BigDecimal: public BigDecimal(BigInteger unscaledVal, int scale)
qui semble pertinent ici, non ? Donnez-lui un BigInteger aléatoire et une échelle aléatoire int, et vous aurez un BigDecimal aléatoire. Non ?
Voici comment je le fais dans une classe appelée Generic_BigInteger disponible via: La Page Web du code source Générique D'Andy Turner
/**
* There are methods to get large random numbers. Indeed, there is a
* constructor for BigDecimal that allows for this, but only for uniform
* distributions over a binary power range.
* @param a_Random
* @param upperLimit
* @return a random integer as a BigInteger between 0 and upperLimit
* inclusive
*/
public static BigInteger getRandom(
Generic_Number a_Generic_Number,
BigInteger upperLimit) {
// Special cases
if (upperLimit.compareTo(BigInteger.ZERO) == 0) {
return BigInteger.ZERO;
}
String upperLimit_String = upperLimit.toString();
int upperLimitStringLength = upperLimit_String.length();
Random[] random = a_Generic_Number.get_RandomArrayMinLength(
upperLimitStringLength);
if (upperLimit.compareTo(BigInteger.ONE) == 0) {
if (random[0].nextBoolean()) {
return BigInteger.ONE;
} else {
return BigInteger.ZERO;
}
}
int startIndex = 0;
int endIndex = 1;
String result_String = "";
int digit;
int upperLimitDigit;
int i;
// Take care not to assign any digit that will result in a number larger
// upperLimit
for (i = 0; i < upperLimitStringLength; i ++){
upperLimitDigit = new Integer(
upperLimit_String.substring(startIndex,endIndex));
startIndex ++;
endIndex ++;
digit = random[i].nextInt(upperLimitDigit + 1);
if (digit != upperLimitDigit){
break;
}
result_String += digit;
}
// Once something smaller than upperLimit guaranteed, assign any digit
// between zero and nine inclusive
for (i = i + 1; i < upperLimitStringLength; i ++) {
digit = random[i].nextInt(10);
result_String += digit;
}
// Tidy values starting with zero(s)
while (result_String.startsWith("0")) {
if (result_String.length() > 1) {
result_String = result_String.substring(1);
} else {
break;
}
}
BigInteger result = new BigInteger(result_String);
return result;
}
Il suffit D'utiliser la réduction modulaire
new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
Compilez ce code F# dans une DLL et vous pouvez également le référencer dans votre C# / VB.NET programmes
type BigIntegerRandom() =
static let internalRandom = new Random()
/// Returns a BigInteger random number of the specified number of bytes.
static member RandomBigInteger(numBytes:int, rand:Random) =
let r = if rand=null then internalRandom else rand
let bytes : byte[] = Array.zeroCreate (numBytes+1)
r.NextBytes(bytes)
bytes.[numBytes] <- 0uy
bigint bytes
/// Returns a BigInteger random number from 0 (inclusive) to max (exclusive).
static member RandomBigInteger(max:bigint, rand:Random) =
let rec getNumBytesInRange num bytes = if max < num then bytes else getNumBytesInRange (num * 256I) bytes+1
let bytesNeeded = getNumBytesInRange 256I 1
BigIntegerRandom.RandomBigInteger(bytesNeeded, rand) % max
/// Returns a BigInteger random number from min (inclusive) to max (exclusive).
static member RandomBigInteger(min:bigint, max:bigint, rand:Random) =
BigIntegerRandom.RandomBigInteger(max - min, rand) + min