Подтвердить что ты не робот

Случайная двойная точка с плавающей запятой в Inclusive Range

Мы можем легко получить случайные числа с плавающей запятой в пределах требуемого диапазона [X,Y) (обратите внимание, что X является инклюзивным, а Y является исключительным) с функцией, указанной ниже, поскольку Math.random() (и большинство генераторов псевдослучайных чисел, AFAIK) производят числа в [0,1):

function randomInRange(min, max) {
  return Math.random() * (max-min) + min;
}
// Notice that we can get "min" exactly but never "max".

Как мы можем получить случайное число в нужном диапазоне включительно для обеих границ, т.е. [X,Y]?

Я полагаю, что мы могли бы "увеличить" наше значение от Math.random() (или эквивалентного) путем "прокатки" бит двойной с плавающей запятой IEE-754 точность, чтобы поставить максимально возможное значение в 1.0 точно, но это похоже на боль, чтобы получить право, особенно на языках, плохо подходящих для манипулирования бит. Есть ли более простой способ?

(В стороне, почему генераторы случайных чисел производят числа в [0,1) вместо [0,1]?)

[Изменить] Обратите внимание: я не нуждаюсь в этом, и я полностью осознаю, что различие является педантичным. Просто быть любопытным и надеяться на некоторые интересные ответы. Не стесняйтесь голосовать, если этот вопрос не подходит.

4b9b3361

Ответ 1

Я считаю, что есть гораздо лучшее решение, но это должно работать:)

function randomInRange(min, max) {
  return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}

Ответ 2

Во-первых, в вашем коде есть проблема: попробуйте randomInRange(0,5e-324) или просто введите Math.random()*5e-324 в консоль JavaScript браузера.

Даже без переполнения/переполнения/денормации трудно обоснованно рассуждать о операциях с плавающей запятой. После небольшого рытья я могу найти контрпример:

>>> a=1.0
>>> b=2**-54
>>> rand=a-2*b
>>> a
1.0
>>> b
5.551115123125783e-17
>>> rand
0.9999999999999999
>>> (a-b)*rand+b
1.0

Легче объяснить, почему это происходит с a = 2 53 и b = 0,5: 2 53 -1 - следующее представляемое число вниз. Режим округления по умолчанию (раунд до ближайшего четного) раундов 2 53 -0,5 up (потому что 2 53 является "четным" [LSB = 0] и 2 53 -1 является "нечетным" [LSB = 1]), поэтому вы вычитаете b и получаете 2 53 умножьте, чтобы получить 2 53 -1, и добавьте b, чтобы снова получить 2 53.


Чтобы ответить на второй вопрос: поскольку базовый PRNG почти всегда генерирует случайное число в интервале [0,2 n -1], то есть генерирует случайные биты. Очень легко выбрать подходящий n (бит точности в вашем представлении с плавающей запятой) и разделить на 2 n и получить предсказуемое распределение. Обратите внимание, что в [0,1) есть некоторые числа, которые вы никогда не будете генерировать с помощью этого метода (ничего в (0,2 -53) с удвоением IEEE).

Это также означает, что вы можете сделать a[Math.floor(Math.random()*a.length)] и не беспокоиться о переполнении (домашняя работа: в бинарной плавающей точке IEEE докажите, что b < 1 означает a*b < a для положительного целого a).

Другое замечательно, что вы можете думать о каждом случайном выходе x как о представлении интервала [x, x + 2 -53) (не очень-то хорошо, что среднее значение возвращается чуть меньше 0,5). Если вы вернетесь в [0,1], возвращаете ли вы конечные точки с той же вероятностью, что и все остальное, или должны ли они иметь только половину вероятности, потому что они представляют только половину интервала как все остальное?

Чтобы ответить на более простой вопрос о возвращении числа в [0,1], метод ниже эффективно генерирует целое число [0,2 n] (путем генерации целого числа в [0,2 n + 1 -1] и отбрасывая его, если он слишком большой) и делятся на 2 n:

function randominclusive() {
  // Generate a random "top bit". Is it set?
  while (Math.random() >= 0.5) {
    // Generate the rest of the random bits. Are they zero?
    // If so, then we've generated 2^n, and dividing by 2^n gives us 1.
    if (Math.random() == 0) { return 1.0; }
    // If not, generate a new random number.
  }
  // If the top bits are not set, just divide by 2^n.
  return Math.random();
}

Комментарии подразумевают базу 2, но я думаю, что эти предположения:

  • 0 и 1 должны быть возвращены равновероятно (т.е. Math.random() не использует более близкое расстояние чисел с плавающей запятой около 0).
  • Math.random() >= 0.5 с вероятностью 1/2 (должно быть верно для четных оснований)
  • Основной PRNG достаточно хорош, чтобы мы могли это сделать.

Обратите внимание, что случайные числа всегда генерируются парами: одно в while (a) всегда сопровождается либо в if, либо в конце (b). Достаточно легко проверить, что это разумно, рассматривая PRNG, который возвращает 0 или 0,5:

  • a=0   b=0  : return 0
  • a=0   b=0.5: return 0.5
  • a=0.5 b=0  : return 1
  • a=0.5 b=0.5: loop

Проблемы:

  • Предположения могут быть неверными. В частности, общий PRNG должен взять верхние 32 бита 48-битного LCG (Firefox и Java делают это). Чтобы сгенерировать double, вы берете 53 бита из двух последовательных выходов и делите на 2 53 но некоторые выходы невозможны (вы не можете генерировать выходы 2 53 с 48 бит государства!). Я подозреваю, что некоторые из них никогда не возвращают 0 (при условии однопоточного доступа), но сейчас я не хочу проверять реализацию Java.
  • Math.random() дважды для каждого потенциального выхода из-за необходимости получить дополнительный бит, но это накладывает больше ограничений на PRNG (требуя, чтобы мы рассуждали о четырех последовательных выходах вышеупомянутого LCG).
  • Math.random() вызывается в среднем около четырех раз на каждый вывод. Немного медленнее.
  • Он отбрасывает результаты детерминистически (при условии однопоточного доступа), поэтому в значительной степени гарантируется сокращение выходного пространства.

Ответ 3

Мое решение этой проблемы всегда заключалось в том, чтобы вместо вашей верхней границы использовать следующее.

Math.nextAfter(upperBound,upperBound+1)

или

upperBound + Double.MIN_VALUE

Итак, ваш код будет выглядеть так:

double myRandomNum = Math.random() * Math.nextAfter(upperBound,upperBound+1) + lowerBound;

или

double myRandomNum = Math.random() * (upperBound + Double.MIN_VALUE) + lowerBound;

Это просто увеличивает вашу верхнюю границу наименьшим двойным (Double.MIN_VALUE), так что ваша верхняя граница будет включена как возможность случайного вычисления.

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

Единственный случай, когда это не сработает, - это то, где ваша верхняя граница равна Double.MAX_VALUE

Ответ 4

Просто выберите свой полуоткрытый интервал немного больше, так что выбранный закрытый интервал является подмножеством. Затем продолжайте генерировать случайную переменную, пока она не приземлится в указанный интервал.

Пример. Если вам нужно что-то однородное в [3,8], затем повторно регенерировать единую случайную переменную в [3,9), пока она не попадет в [3,8].

function randomInRangeInclusive(min,max) {
 var ret;
 for (;;) {
  ret = min + ( Math.random() * (max-min) * 1.1 );
  if ( ret <= max ) { break; }
 }
 return ret;
}

Примечание: количество раз, которое вы генерируете полуоткрытым R.V. является случайным и потенциально бесконечным, но вы можете сделать ожидаемое количество вызовов иначе как близкое к 1, как вам нравится, и я не думаю, что существует решение, которое потенциально не вызывает бесконечно много раз.

Ответ 5

Учитывая "чрезвычайно большое" количество значений от 0 до 1, действительно ли это имеет значение? Шансы нанести первый удар незначительны, поэтому очень маловероятно, чтобы иметь существенное значение для всего, что вы делаете.

Ответ 6

Вопрос сродни запросу: что такое число с плавающей запятой прямо до 1.0? Существует такое число с плавающей запятой, но оно одно в 2 ^ 24 (для IEEE float) или одно в 2 ^ 53 (для a double).

Разница на практике практически невелика.

Ответ 7

Какова будет ситуация, когда вам понадобится значение с плавающей запятой, включающее верхнюю границу? Для целых чисел я понимаю, но для float разница между инклюзивным и эксклюзивным - это то же, что и 1.0e-32.

Ответ 8

Подумайте об этом так. Если вы представляете, что числа с плавающей запятой имеют произвольную точность, вероятность получения точности min равна нулю. Таковы шансы получить max. Я позволю вам сделать для вас свой собственный вывод.

Эта "проблема" эквивалентна получению случайной точки на реальной линии между 0 и 1. Нет "включительно" и "эксклюзивно".

Ответ 9

private static double random(double min, double max) {
    final double r = Math.random();
    return (r >= 0.5d ? 1.5d - r : r) * (max - min) + min;
}

Ответ 10

Я довольно менее опытен, поэтому я также ищу решения.

Это моя грубая мысль:

генераторы случайных чисел производят числа в [0,1] вместо [0,1],

потому что [0,1) - единичная длина, за которой могут следовать [1,2) и т.д. без перекрытия...

Для случайных [x, y], Вы можете сделать это:

float randomInclusive(x, y){

    float MIN = smallest_value_above_zero;
    float result;
    do{
        result = random(x, (y + MIN));
    } while(result > y);
    return result;
}

Здесь все значения в [x, y] могут быть выбраны одинаково, и u теперь может достигнуть y.

Пожалуйста, дайте мне знать, если это не работает или есть потенциальные проблемы.

БЛАГОДАРЯ ~

Ответ 11

Math.round() поможет включить связанное значение. Если у вас 0 <= value < 1 (1 исключение), то Math.round(value * 100) / 100 возвращает 0 <= value <= 1 (1 включен). Обратите внимание, что значение теперь имеет только 2 цифры в десятичной запятой. Если вы хотите 3 цифры, попробуйте Math.round(value * 1000) / 1000 и так далее. Следующая функция имеет еще один параметр, то есть число цифр в десятичном разряде - я называю точностью:

function randomInRange(min, max, precision) {
    return Math.round(Math.random() * Math.pow(10, precision)) /
            Math.pow(10, precision) * (max - min) + min;
}