Je dois générer des entiers aléatoires arbitrairement grands dans la plage de 0 (inclus) à n (exclusif). Ma pensée initiale était d’appeler nextDouble
et de le multiplier par n, mais une fois que n est supérieur à 253, les résultats ne seraient plus distribués uniformément.
BigInteger
dispose du constructeur suivant:
public BigInteger(int numBits, Random rnd)
Construit un BigInteger généré aléatoirement, distribué uniformément sur la plage 0 à (2numBits - 1), inclus.
Comment cela peut-il être utilisé pour obtenir une valeur aléatoire comprise entre 0 et n, où n n’est pas une puissance de 2?
Utilisez une boucle:
BigInteger randomNumber;
do {
randomNumber = new BigInteger(upperLimit.bitLength(), randomSource);
} while (randomNumber.compareTo(upperLimit) >= 0);
en moyenne, il faudra 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 manière suivante:
int nlen = upperLimit.bitLength();
BigInteger nm1 = upperLimit.subtract(BigInteger.ONE);
BigInteger randomNumber, temp;
do {
temp = new BigInteger(nlen + 100, randomSource);
randomNumber = temp.mod(upperLimit);
} while (s.subtract(randomNumber).add(nm1).bitLength() >= nlen + 100);
// result is in 'randomNumber'
Avec cette version, il est hautement improbable que la boucle soit utilisée plus d’une fois (moins d’une chance sur 2 ^ 100, c’est-à-dire bien moins que la probabilité que la machine hôte s’enflamme spontanément dans la seconde). D'autre part, l'opération mod()
est onéreuse en termes de calcul. Cette version est donc probablement plus lente que la précédente, à moins que l'instance randomSource
ne soit exceptionnellement lente.
La méthode suivante utilise le constructeur BigInteger(int numBits, Random rnd)
et rejette le résultat s'il est supérieur au 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 à ceci est que le constructeur s'appelle un nombre indéterminé de fois, mais dans le pire des cas (n est juste légèrement supérieur à une puissance de 2), le nombre attendu d'appels au constructeur ne devrait être que d'environ 2 fois.
La méthode la plus simple (de loin) consiste à utiliser le constructeur spécifié pour générer un nombre aléatoire avec le nombre correct de bits (floor(log2 n) + 1
), puis le jeter s'il est supérieur à n. Dans le pire des cas (par exemple, un nombre compris entre 0 et 2n + 1), vous perdez en moyenne un peu moins de la moitié des valeurs que vous avez créées.
Pourquoi ne pas construire un BigInteger aléatoire, puis construire un BigDecimal à partir de celui-ci? Il existe 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 ?
Il suffit d'utiliser la réduction modulaire
new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
Voici comment je le fais dans une classe appelée Generic_BigInteger disponible via: 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;
}