Случайное число с плавающей запятой в Инклюзивном диапазоне - PullRequest
25 голосов
/ 15 марта 2012

Мы можем легко получить случайные числа с плавающей запятой в желаемом диапазоне [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]?)

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

Ответы [ 11 ]

13 голосов
/ 15 марта 2012

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

function randomInRange(min, max) {
  return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
5 голосов
/ 22 сентября 2013

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

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

5 голосов
/ 16 марта 2012

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

Даже без переполнения / недостаточного / денормса трудно надежно рассуждать о плавающемPoint Ops.Немного покопавшись, я могу найти контрпример:

>>> 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 (поскольку 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 : возврат 0
  • a=0 b=0.5: возврат 0,5
  • a=0.5 b=0 : возвращение 1
  • a=0.5 b=0.5: цикл

Проблемы:

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

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

Пример: если вы хотите что-то равномерное в [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;
}

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

1 голос
/ 15 марта 2012

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

0 голосов
/ 21 февраля 2017

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

function randomInRange(min, max, precision) {
    return Math.round(Math.random() * Math.pow(10, precision)) /
            Math.pow(10, precision) * (max - min) + min;
}
0 голосов
/ 18 июля 2014

Я немного менее опытен, поэтому я тоже ищу решения.

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

генераторы случайных чисел производят числа в [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] имеют одинаковую возможность выбора, и вы можете достичь y сейчас.

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

СПАСИБО ~

0 голосов
/ 25 ноября 2013
private static double random(double min, double max) {
    final double r = Math.random();
    return (r >= 0.5d ? 1.5d - r : r) * (max - min) + min;
}
0 голосов
/ 15 марта 2012

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

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

0 голосов
/ 15 марта 2012

В какой ситуации вам НУЖНО значение с плавающей запятой, включающее верхнюю границу? Я понимаю, что для целых чисел, но для числа с плавающей точкой разница между инклюзивным и эксклюзивным такая же, как 1.0e-32.

...