javascript - Doble punto aleatorio doble en rango inclusivo
random language-agnostic (11)
Podemos obtener fácilmente números de coma flotante aleatorios dentro de un rango deseado [X,Y)
(tenga en cuenta que X es inclusivo e Y es exclusivo) con la función enumerada a continuación ya que Math.random()
(y la mayoría de los generadores de números pseudoaleatorios, AFAIK) producen números en [0,1)
:
function randomInRange(min, max) {
return Math.random() * (max-min) + min;
}
// Notice that we can get "min" exactly but never "max".
¿Cómo podemos obtener un número aleatorio en un rango deseado inclusive para ambos límites, es decir [X,Y]
?
Supongo que podríamos "incrementar" nuestro valor de Math.random()
(o equivalente) "rodando" los bits de una IEE-754 punto flotante de precisión doble para poner el valor máximo posible en 1.0 exactamente, pero eso parece una molestia para acertar, especialmente en idiomas poco adecuados para la manipulación de bits. hay una manera mas facil?
(Como un aparte, ¿por qué los generadores de números aleatorios producen números en [0,1)
lugar de [0,1]
?)
[Editar] Tenga en cuenta que no necesito esto y soy plenamente consciente de que la distinción es pedante. Siendo curioso y esperando algunas respuestas interesantes. Siéntase libre de votar para cerrar si esta pregunta es inapropiada.
¿Cuál sería una situación en la que NECESITARÍA un valor de punto flotante para incluir el límite superior? Para enteros, lo entiendo, pero para un float, la diferencia entre inclusivo y exclusivo es como 1.0e-32.
Creo que hay una decisión mucho mejor, pero esta debería funcionar :)
function randomInRange(min, max) {
return Math.random() < 0.5 ? ((1-Math.random()) * (max-min) + min) : (Math.random() * (max-min) + min);
}
Dado el número "extremadamente grande" de valores entre 0 y 1, ¿realmente importa? Las posibilidades de llegar a 1 son mínimas, por lo que es muy poco probable que hagan una diferencia significativa en lo que haces.
La pregunta es similar a preguntar, ¿cuál es el número de coma flotante justo antes de 1.0? Hay un número de coma flotante, pero es uno en 2 ^ 24 (para un float
IEEE) o uno en 2 ^ 53 (para un double
).
La diferencia es insignificante en la práctica.
Mi solución a este problema siempre ha sido utilizar lo siguiente en lugar de su límite superior.
Math.nextAfter(upperBound,upperBound+1)
o
upperBound + Double.MIN_VALUE
Entonces su código se vería así:
double myRandomNum = Math.random() * Math.nextAfter(upperBound,upperBound+1) + lowerBound;
o
double myRandomNum = Math.random() * (upperBound + Double.MIN_VALUE) + lowerBound;
Esto simplemente incrementa su límite superior con el doble más pequeño ( Double.MIN_VALUE
) de modo que su límite superior se incluirá como una posibilidad en el cálculo aleatorio.
Esta es una buena forma de hacerlo porque no sesga las probabilidades a favor de un solo número.
El único caso en el que esto no funcionaría es donde su límite superior es igual a Double.MAX_VALUE
Piénsalo de esta manera. Si imagina que los números de punto flotante tienen una precisión arbitraria, las posibilidades de obtener exactamente el min
son cero. Entonces, ¿son las posibilidades de obtener max
. Te dejaré sacar tu propia conclusión sobre eso.
Este ''problema'' es equivalente a obtener un punto aleatorio en la línea real entre 0 y 1. No hay ''inclusivo'' y ''exclusivo''.
Primero, hay un problema en tu código: prueba randomInRange(0,5e-324)
o simplemente ingresa Math.random()*5e-324
en la consola de JavaScript de tu navegador.
Incluso sin overflow / underflow / denorms, es difícil razonar de manera confiable acerca de operaciones con coma flotante. Después de excavar un poco, puedo encontrar un contraejemplo:
>>> 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
Es más fácil explicar por qué sucede esto con a = 2 53 y b = 0.5: 2 53 -1 es el siguiente número representable hacia abajo. El modo de redondeo predeterminado ("redondea al par más cercano") redondea 2 53 -0.5 hacia arriba (porque 2 53 es "par" [LSB = 0] y 2 53 -1 es "impar" [LSB = 1]), por lo que resta b
y obtenga 2 53 , multiplique para obtener 2 53 -1, y agregue b
para obtener 2 53 nuevamente.
Para responder a su segunda pregunta: Porque el PRNG subyacente casi siempre genera un número aleatorio en el intervalo [0,2 n -1], es decir, genera bits aleatorios. Es muy fácil elegir un n adecuado (los bits de precisión en su representación en coma flotante) y dividir entre 2 ny obtener una distribución predecible. Tenga en cuenta que hay algunos números en [0,1)
que nunca se generarán utilizando este método (cualquier cosa en (0,2 -53 ) con dobles de IEEE).
También significa que puede hacer a[Math.floor(Math.random()*a.length)]
y no preocuparse por el desbordamiento (deberes: en el punto flotante binario IEEE, pruebe que b < 1
implica a*b < a
para número entero positivo a
).
La otra cosa agradable es que se puede pensar en cada salida aleatoria x como representando un intervalo [x, x + 2 -53 ) (lo que no es tan agradable es que el valor promedio devuelto sea ligeramente inferior a 0,5). Si regresas en [0,1], ¿devuelves los puntos finales con la misma probabilidad que todo lo demás, o solo tienen la mitad de la probabilidad porque solo representan la mitad del intervalo como todo lo demás?
Para responder a la pregunta más simple de devolver un número en [0,1], el método siguiente genera efectivamente un entero [0,2 n ] (generando un número entero en [0,2 n + 1 -1] y tirándolo si es demasiado grande) y se divide por 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();
}
Los comentarios implican base 2, pero creo que las suposiciones son así:
- 0 y 1 deben devolverse equiprobably (es decir, Math.random () no utiliza el espaciado más cercano de los números de coma flotante cerca de 0).
- Math.random ()> = 0.5 con probabilidad 1/2 (debe ser cierto para bases pares)
- El PRNG subyacente es lo suficientemente bueno como para que podamos hacer esto.
Tenga en cuenta que los números aleatorios siempre se generan en pares: el que está en el while
(a) siempre va seguido por el que está en el if
o el que está al final (b). Es bastante fácil verificar que sea sensato considerando un PRNG que devuelva 0 o 0.5:
-
a=0 b=0
: retorno 0 -
a=0 b=0.5
: retorno 0.5 -
a=0.5 b=0
: retorno 1 -
a=0.5 b=0.5
: lazo
Problemas:
- Las suposiciones pueden no ser ciertas. En particular, un PRNG común es tomar los 32 bits superiores de un LCG de 48 bits (Firefox y Java hacen esto). Para generar un doble, toma 53 bits de dos salidas consecutivas y divide por 2 53 , pero algunas salidas son imposibles (¡no puede generar 2 53 salidas con 48 bits de estado!). Sospecho que algunos de ellos nunca devuelven 0 (asumiendo el acceso de un único subproceso), pero no tengo ganas de verificar la implementación de Java en este momento.
- Math.random () es dos veces para cada resultado potencial como consecuencia de la necesidad de obtener el bit adicional, pero esto impone más restricciones al PRNG (lo que requiere que razonemos sobre cuatro salidas consecutivas del LCG anterior).
- Math.random () se llama en promedio unas cuatro veces por salida. Un poco lento.
- Elimina los resultados de manera determinista (suponiendo el acceso de un único subproceso), por lo que se garantiza que reducirá el espacio de salida.
Simplemente elija el intervalo medio abierto ligeramente más grande, de modo que el intervalo cerrado elegido sea un subconjunto. Luego, siga generando la variable aleatoria hasta que aterrice en dicho intervalo cerrado.
Ejemplo: Si quieres algo uniforme en [3,8], entonces regenera repetidamente una variable aleatoria uniforme en [3,9] hasta que aterrice en [3,8].
function randomInRangeInclusive(min,max) {
var ret;
for (;;) {
ret = min + ( Math.random() * (max-min) * 1.1 );
if ( ret <= max ) { break; }
}
return ret;
}
Nota: La cantidad de veces que genera el RV semiabierto es aleatorio y potencialmente infinito, pero puede hacer que el número esperado de llamadas sea lo más cercano posible a 1, y no creo que exista una solución que no lo haga. t potencialmente llamar infinitamente muchas veces.
Tengo bastante menos experiencia, así que también estoy buscando soluciones.
Este es mi pensamiento aproximado:
los generadores de números aleatorios producen números en [0,1) en lugar de [0,1],
porque [0,1) es una longitud de unidad que puede ser seguido por [1,2) y así sucesivamente sin superposición ...
Para al azar [x, y], puedes hacer esto:
float randomInclusive(x, y){
float MIN = smallest_value_above_zero;
float result;
do{
result = random(x, (y + MIN));
} while(result > y);
return result;
}
Donde todos los valores en [x, y] tienen la misma posibilidad de ser recogidos, y usted puede alcanzar y ahora.
Por favor, avíseme si esto no funciona o si tiene problemas potenciales.
GRACIAS ~
Math.round()
ayudará a incluir el valor enlazado. Si tiene 0 <= value < 1
(1 es exclusivo), Math.round(value * 100) / 100
devuelve 0 <= value <= 1
(1 es inclusivo). Una nota aquí es que el valor ahora tiene solo 2 dígitos en su lugar decimal. Si quieres 3 dígitos, prueba Math.round(value * 1000) / 1000
y así sucesivamente. La siguiente función tiene un parámetro más, es decir, el número de dígitos en el lugar decimal. Llamé como precisión :
function randomInRange(min, max, precision) {
return Math.round(Math.random() * Math.pow(10, precision)) /
Math.pow(10, precision) * (max - min) + min;
}
private static double random(double min, double max) {
final double r = Math.random();
return (r >= 0.5d ? 1.5d - r : r) * (max - min) + min;
}