2010-11-15 27 views
38

The documentation básicamente dice que range debe comportarse exactamente como esta implementación (por step positivo):Rango con el paso de tipo float

def range(start, stop, step): 
    x = start 
    while True: 
    if x >= stop: return 
    yield x 
    x += step 

También dice que sus argumentos deben ser números enteros. ¿Porqué es eso? ¿No es esa definición también perfectamente válida si el paso es un flotador?

En mi caso, estoy especialmente. que necesita una función range que acepta un tipo de letra flotante como su argumento step. ¿Hay alguno en Python o necesito implementar el mío?


más específica: ¿Cómo voy a traducir el código C directamente a Python de una manera agradable (es decir, no sólo lo hace a través de un while lazos en forma manual):

for(float x = 0; x < 10; x += 0.5f) { /* ... */ } 
+0

No puede tener las palabras clave 'return' y' yield' en ese bucle, use 'break'. –

+4

@Tim: Claro que puedo. – Albert

+0

¡Parece que puedes! No pensé que eso fuera posible. Estoy seguro de que se han producido errores cuando intenté algo similar. –

Respuesta

44

Puede usar numpy.arange.

EDITAR: Los documentos prefieren numpy.linspace. Gracias @Droogans para tener en cuenta =)

+3

El primer párrafo indica: Al usar un paso no entero, como 0.1, los resultados a menudo no serán consistentes. Es mejor usar ['linspace'] (http://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html?highlight=linspace#numpy.linspace) para estos casos. – Droogans

+2

Tenga en cuenta que 'linspace' tiene una interfaz diferente. –

+0

O incluso sin numpy.arange. Hice una función xfrange sin los problemas de precisión de flotación. Échale un vistazo;) http://stackoverflow.com/questions/477486/python-decimal-range-step-value/20549652#20549652 –

-1

Probablemente porque se puede' t tiene parte de un iterable. Además, floats son imprecisos.

+0

Los flotadores son muy precisos. Lo que no son buenos es representar ciertos valores que tienen una representación finita en la base 10. Por ejemplo, 0.1 en la base 10 se convertirá en 0x1.999999999999ap-4 (usando la representación exacta de flotación hexadecimal) en tu memoria que no es exactamente 0.1 en decimal . Con ese argumento también puede decir que los números decimales son imprecisos porque hay ciertos valores que no tienen representación finita para ... – josch

4

Cuando agrega números flotantes juntos, a menudo hay un poco de error. ¿Un range(0.0, 2.2, 1.1) devolvería [0.0, 1.1] o [0.0, 1.1, 2.199999999]? No hay forma de estar seguro sin un análisis riguroso.

El código que ha publicado es una buena solución si realmente lo necesita. Solo tenga en cuenta las posibles deficiencias.

+1

Sí, hay una manera de estar seguro. Su tamaño de paso de 1.1 es exactamente 0x1.199999999999ap + 0 cuando se representa como un número doble de coma flotante en su memoria. El código que OP publicó no es una solución perfecta porque la adición repetida de valores imprecisos permite que el error se acumule. Es una mejor idea realizar un seguimiento del número de bucle durante la iteración y multiplicar el tamaño del paso por el número de bucle como ya se han mostrado otras respuestas. – josch

+0

@josch Quise decir en el caso general, no esos números específicamente. –

+0

Entonces nuestras definiciones de "ciertas" difieren. Incluso con números flotantes, una computadora no le dará un resultado "incierto" o "aleatorio". El resultado siempre será determinista. Independientemente de la entrada, dado cualquier número de punto flotante finito, siempre podrá decir con certeza cuál es su resultado después de agregarlos X veces con una precisión dada. – josch

30

Una explicación podría ser problemas de redondeo de coma flotante. Por ejemplo, si se puede llamar

range(0, 0.4, 0.1) 

se podría esperar una producción de

[0, 0.1, 0.2, 0.3] 

pero en realidad se trata de algo así como

[0, 0.1, 0.2000000001, 0.3000000001] 

debido a problemas de redondeo. Y dado que el rango se usa a menudo para generar índices de algún tipo, solo es un número entero.

Aún así, si desea un generador de rango para flotadores, puede simplemente hacer el suyo.

def xfrange(start, stop, step): 
    i = 0 
    while start + i * step < stop: 
     yield start + i * step 
     i += 1 
+0

Sí, por supuesto (es natural que se comporte de esa manera). Ese es siempre el caso con flotadores. Así que, por supuesto, sería totalmente legítimo que si 'range 'admitiría flotadores, se comportaría de esa manera.Entonces realmente no entendí por qué no debería ser así. – Albert

+0

Sí, tienes razón. Lo mejor que se me ocurre es "es impreciso". Y "¿tal vez sea necesario por algún detalle de implementación común?" – Zarkonnen

+1

@Albert: generalmente no tiene mucho sentido preguntarse por qué se tomaron decisiones como esta; simplemente lo fueron. En este caso, los casos de uso limitado se verían superados por el potencial de errores desagradables. – katrielalex

6

El problema con el punto flotante es que es posible que no obtenga el mismo número de elementos que esperaba debido a la inexactitud. Esto puede ser un problema real si juegas con polinomios donde la cantidad exacta de elementos es bastante importante.

Lo que realmente quieres es una progresión aritmética; el siguiente código funcionará muy felizmente para int, float y complex ... y cadenas, y listas ...

def arithmetic_progression(start, step, length): 
    for i in xrange(length): 
     yield start + i * step 

Tenga en cuenta que este código tiene una mejor oportunidad de su último valor está dentro rugido de un toro del valor esperado que cualquier otra alternativa que mantiene un total acumulado.

>>> 10000 * 0.0001, sum(0.0001 for i in xrange(10000)) 
(1.0, 0.9999999999999062) 
>>> 10000 * (1/3.), sum(1/3. for i in xrange(10000)) 
(3333.333333333333, 3333.3333333337314) 

Corrección: aquí hay un competetive running-total gadget:

def kahan_range(start, stop, step): 
    assert step > 0.0 
    total = start 
    compo = 0.0 
    while total < stop: 
     yield total 
     y = step - compo 
     temp = total + y 
     compo = (temp - total) - y 
     total = temp 

>>> list(kahan_range(0, 1, 0.0001))[-1] 
0.9999 
>>> list(kahan_range(0, 3333.3334, 1/3.))[-1] 
3333.333333333333 
>>> 
+0

Esta es una respuesta maravillosa. ¿Por qué no obtuvo más votaciones ascendentes que las respuestas que acumulan imprecisión o necesitan bibliotecas adicionales? – josch

12

Con el fin de ser capaz de utilizar los números decimales en una expresión de rango de una manera fresca para hacerlo es la siguiente: [x * 0.1 para x en el rango (0, 10)]

+2

Si su "rango" tiene muchos números y su "paso" de 0.1 es más pequeño, digamos .00000001, entonces esto no funcionará y la secuencia de comandos se bloqueará en la mayoría de las computadoras. Necesitaremos algo que simule lo que hace xrange.es. Necesitamos un iterable/generador, y no una lista de comprensión. El rango de Rango anterior funciona mejor en este caso. – ekta

+1

¡Solución muy simple para la comprensión de la lista, gracias! – avtomaton

+1

@ekta Use '()' en lugar de '[]' para convertirlo en un generador. –

3

Aquí es un caso especial que podría ser lo suficientemente bueno:

[ (1.0/divStep)*x for x in range(start*divStep, stop*divStep)] 

En su caso esto sería:

#for(float x = 0; x < 10; x += 0.5f) { /* ... */ } ==> 
start = 0 
stop = 10 
divstep = 1/.5 = 2 #This needs to be int, thats why I said 'special case' 

y así:

>>> [ .5*x for x in range(0*2, 10*2)] 
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5] 
1

Esto es lo que yo usaría:

numbers = [float(x)/10 for x in range(10)] 

en lugar de:

numbers = [x*0.1 for x in range(10)] 
that would return : 
[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9] 

espero que él lps.

+0

Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar aclaraciones de un autor, deje un comentario debajo de su publicación; siempre puede comentar sus propias publicaciones, y una vez que tenga suficiente [reputación] (http://stackoverflow.com/help/whats-reputation) lo hará poder [comentar cualquier publicación] (http://stackoverflow.com/help/privileges/comment). –

+0

@RaisAlam Tenga en cuenta que si usó 0.5 en lugar de 0.1, el comportamiento inesperado no aparecería. –

Cuestiones relacionadas