Ответ 1
Я считаю, что есть гораздо лучшее решение, но это должно работать:)
function randomInRange(min, max) {
return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
Мы можем легко получить случайные числа с плавающей запятой в пределах требуемого диапазона [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]
?)
[Изменить] Обратите внимание: я не нуждаюсь в этом, и я полностью осознаю, что различие является педантичным. Просто быть любопытным и надеяться на некоторые интересные ответы. Не стесняйтесь голосовать, если этот вопрос не подходит.
Я считаю, что есть гораздо лучшее решение, но это должно работать:)
function randomInRange(min, max) {
return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
Во-первых, в вашем коде есть проблема: попробуйте 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, но я думаю, что эти предположения:
Обратите внимание, что случайные числа всегда генерируются парами: одно в while
(a) всегда сопровождается либо в if
, либо в конце (b). Достаточно легко проверить, что это разумно, рассматривая PRNG, который возвращает 0 или 0,5:
a=0 b=0
: return 0a=0 b=0.5
: return 0.5a=0.5 b=0
: return 1a=0.5 b=0.5
: loopПроблемы:
Мое решение этой проблемы всегда заключалось в том, чтобы вместо вашей верхней границы использовать следующее.
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
Просто выберите свой полуоткрытый интервал немного больше, так что выбранный закрытый интервал является подмножеством. Затем продолжайте генерировать случайную переменную, пока она не приземлится в указанный интервал.
Пример. Если вам нужно что-то однородное в [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, как вам нравится, и я не думаю, что существует решение, которое потенциально не вызывает бесконечно много раз.
Учитывая "чрезвычайно большое" количество значений от 0 до 1, действительно ли это имеет значение? Шансы нанести первый удар незначительны, поэтому очень маловероятно, чтобы иметь существенное значение для всего, что вы делаете.
Вопрос сродни запросу: что такое число с плавающей запятой прямо до 1.0? Существует такое число с плавающей запятой, но оно одно в 2 ^ 24 (для IEEE float
) или одно в 2 ^ 53 (для a double
).
Разница на практике практически невелика.
Какова будет ситуация, когда вам понадобится значение с плавающей запятой, включающее верхнюю границу? Для целых чисел я понимаю, но для float разница между инклюзивным и эксклюзивным - это то же, что и 1.0e-32.
Подумайте об этом так. Если вы представляете, что числа с плавающей запятой имеют произвольную точность, вероятность получения точности min
равна нулю. Таковы шансы получить max
. Я позволю вам сделать для вас свой собственный вывод.
Эта "проблема" эквивалентна получению случайной точки на реальной линии между 0 и 1. Нет "включительно" и "эксклюзивно".
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,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.
Пожалуйста, дайте мне знать, если это не работает или есть потенциальные проблемы.
БЛАГОДАРЯ ~
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;
}