Реализация java.util.Random.nextInt

Эта функция от java.util.Random. Он возвращает псевдослучайное выражение int, равномерно распределенное между 0 и данным n. К сожалению, я этого не понимал.

public int nextInt(int n) {
    if (n <= 0)
        throw new IllegalArgumentException("n must be positive");

    if ((n & -n) == n)  // i.e., n is a power of 2
        return (int)((n * (long)next(31)) >> 31);

    int bits, val;
    do {
        bits = next(31);
        val = bits % n;
    } while (bits - val + (n-1) < 0);
    return val;
}

Мои вопросы:

  • Почему он относится к случаю, когда n - это сила двух специально? Это просто для производительности?
  • Зачем это делать, чтобы отклонить числа, которые bits - val + (n-1) < 0?

Ответ 1

next генерирует случайные биты.

  • Когда n является степенью 2, случайное целое в этом диапазоне может быть сгенерировано только путем генерации случайных битов (я полагаю, что всегда генерация 31 и отбрасывание некоторых из них - для воспроизводимости). Этот путь кода проще, и я предполагаю, что это более часто используемый случай, поэтому стоит сделать специальный "быстрый путь" для этого случая.

  • Когда n не является степенью 2, он выбрасывает числа в "верхнем" диапазоне, чтобы случайное число равномерно распределялось. Например. представьте, что мы имели n=3, и предположим, что мы использовали 3 бита, а не 31 бит. Таким образом, bits - это случайное число от 0 до 7. Как вы можете создать там справедливое случайное число? Ответ: если bits равно 6 или 7, мы отбрасываем его и генерируем новый.

Ответ 2

Это делается для обеспечения равномерного распределения значений между 0 и n. У вас может возникнуть соблазн сделать что-то вроде:

int x = rand.nextInt() % n;

но это изменит распределение значений, если n не является делителем 2^31, то есть степенью 2. Это связано с тем, что по модулю оператор будет производить классы эквивалентности, размер которых не является одинаковым.

Например, допустим, что nextInt() генерирует целое число от 0 до 6 включительно, и вы хотите нарисовать 0,1 или 2. Легко, правильно?

int x = rand.nextInt() % 3;

Нет. Давайте посмотрим, почему:

0 % 3 = 0
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
5 % 3 = 2
6 % 3 = 0

Итак, у вас есть 3 значения, которые отображают на 0 и только 2 значения, которые отображаются на 1 и 2. У вас теперь есть смещение, так как 0 более вероятно будет возвращено, чем 1 или 2.

Как всегда, javadoc документирует это поведение:

Хедж "приблизительно" используется только в приведенном выше описании потому что следующий метод является лишь приблизительно беспристрастным источником независимо выбранных битов. Если бы это был идеальный источник случайного выбранных битов, тогда показанный алгоритм выбирает значения int из заявленный диапазон с идеальной однородностью.

Алгоритм немного сложный. Он отклоняет значения, которые приведут к в неравномерном распределении (из-за того, что 2 ^ 31 не делится по n). Вероятность отклонения значения зависит от n. худшим случаем является n = 2 ^ 30 + 1, для которого вероятность отклонения равна 1/2, и ожидаемое число итераций до завершения цикла равно 2.

Алгоритм рассматривает случай, когда n является степенью двух особых: it возвращает правильное количество бит высокого порядка из основного генератор псевдослучайных чисел. В отсутствие специального лечения, будет возвращено правильное количество младших бит. линейный конгруэнтные генераторы псевдослучайных чисел, такие как как известно, имеют короткие периоды в последовательность значений их младших разрядов. Таким образом, этот частный случай значительно увеличивает длину последовательности значений, возвращаемых последовательные вызовы этого метода, если n - малая степень двух.

Акцент мой.