0(包括的)からn(包括的)の範囲の任意の大きなランダム整数を生成する必要があります。私の最初の考えは、 nextDouble
を呼び出してnを掛けることでしたが、nが2より大きくなると53、結果はもはや均一に分布しません。
BigInteger
には次のコンストラクターがあります。
public BigInteger(int numBits, Random rnd)
ランダムに生成されたBigIntegerを構築し、範囲0から(2numBits -1)、包括的。
これを使用して0〜nの範囲のランダムな値を取得するにはどうすればよいですか(nは2のべき乗ではありません)。
ループを使用します。
_BigInteger randomNumber;
do {
randomNumber = new BigInteger(upperLimit.bitLength(), randomSource);
} while (randomNumber.compareTo(upperLimit) >= 0);
_
平均して、これは2回未満の反復で済み、選択は均一になります。
編集: RNGが高価な場合は、次の方法で反復回数を制限できます。
_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'
_
このバージョンでは、ループが複数回(2 ^ 100で1回未満、つまり確率よりはるかに少ない)実行される可能性は非常に低いホストマシンが次の2秒で自発的に発火すること)。一方、mod()
操作は計算コストが高いため、randomSource
インスタンスが非常に遅い場合を除き、このバージョンはおそらく以前のものよりも遅くなります。
次のメソッドはBigInteger(int numBits, Random rnd)
コンストラクターを使用し、指定されたnよりも大きい場合、結果を拒否します。
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;
}
これの欠点は、コンストラクターが指定されていない回数呼び出されることですが、最悪の場合(nは2のべき乗よりもわずかに大きい)、コンストラクターの予想される呼び出し回数は約2回である必要があります。
最も単純なアプローチ(かなり長い方法で)は、指定されたコンストラクターを使用して適切なビット数(floor(log2 n) + 1
)の乱数を生成し、nより大きい場合は破棄します。最悪の場合(例:[0、2n + 1)作成した値の半分以下を平均して捨てます。
ランダムなBigIntegerを構築し、それからBigDecimalを構築しないのはなぜですか? BigDecimalにはコンストラクターがあります:public BigDecimal(BigInteger unscaledVal, int scale)
これはここでは関連しているように見えますが、いいえ?ランダムなBigIntegerとランダムなスケールintを与えると、ランダムなBigDecimalが得られます。いや?
モジュラーリダクションを使用するだけ
new BigInteger(n.bitLength(), new SecureRandom()).mod(n)
以下に、Generic_BigIntegerというクラスで利用できる方法を示します。 Andy TurnerのGeneric Source Code Web Page
/**
* 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;
}