Как создать случайное значение BigInteger в Java?
мне нужно сгенерировать произвольно большие случайные целые числа в диапазоне от 0 (включительно) до n (эксклюзив). Моей первой мыслью было позвонить nextDouble и умножить на n, но как только n становится больше 253, результаты не будут равномерно распределены.
BigInteger имеет следующий конструктор:
public BigInteger(int numBits, Random rnd)
создает случайно сгенерированный BigInteger, равномерно распределенный по диапазону 0 до (2кол-во бит - 1) включительно.
как это можно использовать для получения случайного значения в диапазоне 0-n, где n не является степенью 2?
7 ответов:
использовать цикл:
BigInteger r; do { r = new BigInteger(n.bitLength(), rnd); } while (r.compareTo(n) >= 0);в среднем для этого потребуется менее двух итераций, и выбор будет равномерным.
Edit: если ваш ГСЧ дорого, вы можете ограничить количество итераций следующим образом:
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'С этой версией крайне маловероятно, что цикл берется более одного раза (менее одного шанса в 2^100, т. е. гораздо меньше, чем вероятность того, что хост-машина спонтанно загорается в следующую секунду). С другой стороны,
mod()операция является вычислительно дорогой, поэтому эта версия, вероятно, медленнее, чем предыдущая, если толькоrndнапример, если очень медленно.
следующий метод использует
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. Нет ?
вот как я это делаю в классе под названием Generic_BigInteger доступны через: общий исходный код веб-страницы Энди Тернера
/** * 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; }
скомпилируйте этот код F# в DLL, и вы также можете ссылаться на него в своем C# / VB.NET программы
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
Comments