2010-06-14 5 views
10

Al investigar para this question y leer el código fuente en random.py, empecé a preguntarme si randrange y randint realmente se comportan como "anunciados". Estoy muy inclinado a creer así, pero la forma en que lo leí, randrange se implementa esencialmente como¿Podría random.randint (1,10) alguna vez devolver 11?

start + int(random.random()*(stop-start)) 

(suponiendo valores enteros para start y stop), de modo randrange(1, 10) debería devolver un número aleatorio entre 1 y 9.

randint(start, stop)randrange(start, stop+1) está llamando, volviendo de este modo un número entre 1 y 10.

Mi pregunta es ahora:

Si random() alguna vez devolvieran 1.0, entonces randint(1,10) devolvería 11, ¿o no?

+0

Por cierto, 'int (random.random() * n)' todavía no es una forma perfecta de generar enteros que se distribuyen uniformemente en 'range (n)'; hay un sesgo que es insignificante para 'n' pequeña pero se vuelve significativo a medida que' n' se vuelve grande. Abrí un error de Python para esto en http://bugs.python.org/issue9025 –

+0

@Mark Dickinson: ¡Gracias! Esto es fascinante –

+0

@Mark Dickinson: [Este error se corrigió a partir de hoy] (http://docs.python.org/dev/whatsnew/3.2.html#random). –

Respuesta

26

De random.py y los documentos:

"""Get the next random number in the range [0.0, 1.0).""" 

El ) indica que el intervalo es exclusiva 1.0. Es decir, nunca regresará 1.0.

Esta es una convención general en matemáticas, [ y ] es inclusivo, mientras ( y ) es exclusiva, y los dos tipos de paréntesis se pueden mezclar como (a, b] o [a, b). Eche un vistazo al wikipedia: Interval (mathematics) para obtener una explicación formal.

+0

No había captado ese ')' (y aunque lo hubiera hecho, no habría sabido su significado, así que muchas gracias por esta respuesta perspicaz). –

+1

@Tim: FYI, existen varias convenciones diferentes. Otra convención comúnmente utilizada es invertir los corchetes cuadrados, de modo que '[a, b [' sería un intervalo medio abierto equivalente a '[a, b)'. –

+5

Esto no es suficiente, ya que no es obvio que '0.0 <= x <1.0' implica que' 0 <= x * n

3

De documentación Python:

Casi todas las funciones del módulo dependen de la función básica aleatorio(), que genera un flotador aleatorio uniformemente en el intervalo semiabierto [0.0, 1.0).

Como casi todos los PRNG de números flotantes ..

12

Otras respuestas han señalado que el resultado de random() es siempre estrictamente menos de 1.0; sin embargo, eso es solo la mitad de la historia.

Si está calculando randrange(n) como int(random() * n), que también necesita saber que para cualquier flotador Python x satisfacer 0.0 <= x < 1.0, y cualquier número entero positivo n, es cierto que 0.0 <= x * n < n, de manera que int(x * n) es estrictamente menor que n.

Hay dos cosas que podrían salir mal aquí: en primer lugar, cuando calculamos x * n, n se convierte implícitamente en un flotante. Para un tamaño suficientemente grande n, esa conversión podría alterar el valor.Pero si nos fijamos en la fuente de Python, verá que solo utiliza el método int(random() * n) para n menor que 2**53 (aquí y abajo supongo que la plataforma usa dobles IEEE 754), que es el rango donde la conversión de n a un flotador se garantiza que no perderá información (porque n se puede representar exactamente como un flotador).

Lo segundo que podría ir mal es que el resultado de la multiplicación x * n (que ahora se realiza como un producto de flotadores, recuerde) probablemente no será exactamente representable, por lo que habrá un poco de redondeo. Si x está lo suficientemente cerca de 1.0, es concebible que el redondeo redondee el resultado hasta el n. Para ver que esto no puede suceder, solo tenemos que considerar el mayor valor posible para x, que es (en casi todas las máquinas en las que se ejecuta Python) 1 - 2**-53. Entonces, tenemos que mostrar (1 - 2**-53) * n < n para nuestro entero positivo n, ya que siempre será cierto que random() * n <= (1 - 2**-53) * n.

Prueba (Boceto) Que k ser el único entero tal que k2**(k-1) < n <= 2**k. Luego, el siguiente float desde n es n - 2**(k-53). Necesitamos mostrar que n*(1-2**53) (es decir, el valor real, no redondeado, del producto) está más cerca de n - 2**(k-53) que de n, por lo que siempre se redondeará hacia abajo. Pero un poco de aritmética muestra que la distancia de n*(1-2**-53) a n es 2**-53 * n, mientras que la distancia de n*(1-2**-53) a n - 2**(k-53) es (2**k - n) * 2**-53. Pero 2**k - n < n (porque elegimos k modo que 2**(k-1) < n), por lo que el producto es más cerca de n - 2**(k-53), por lo que se obtener redondeado hacia abajo (suponiendo, es decir, que la plataforma está haciendo algún tipo de ronda a más cercano) .

Estamos seguros. ¡Uf!


Addendum (07/04/2015): El anterior supone IEEE 754 binary64 aritmética, con redondas lazos-a-incluso modo de redondeo. En muchas máquinas, esa suposición es bastante segura. Sin embargo, en máquinas x86 que usan la FPU x87 para punto flotante (por ejemplo, varios sabores de Linux de 32 bits), existe la posibilidad de double rounding en la multiplicación, y eso hace posible random() * n redondear hasta a n en el caso en que random() devuelve el mayor valor posible. El más pequeño n para el que esto puede suceder es n = 2049. Consulte la discusión en http://bugs.python.org/issue24546 para obtener más información.

Cuestiones relacionadas