¿Cómo utilizar un valor de paso de rango decimal ()?

744

¿Hay alguna manera de pasar de 0 a 1 por 0.1?

Pensé que podría hacerlo de la siguiente manera, pero falló:

for i in range(0, 1, 0.1):
    print i

En cambio, dice que el argumento paso no puede ser cero, lo cual no esperaba.

Evan Fosmark
fuente
17
int (0.1) == 0, por lo que el paso en realidad es cero. Puede ser inesperado, pero es cero. Es posible que desee repetir su pregunta para reflejar el hecho de que no esperaba esto. Decir "no lo es" es falso y engañoso.
S.Lott
3
Por cierto, se puede enrollar una línea corta usando itertools.takewhiley itertools.count. Sin drangeembargo, no es mejor que el rendimiento.
Kos
1
Es vergonzoso que el rango de Python no permita esto, dado lo fácil que es implementar un generador que lo haga incluso sin acumular errores de redondeo. Diablos, ¡incluso la seqherramienta en GNU coreutils le permite a uno hacerlo seq 0 0.1 1sin errores de redondeo!
josch
3
@josch: sequtiliza el long doubletipo C internamente y está sujeto a errores de redondeo. Por ejemplo, en mi máquina, seq 0 0.1 1da 1como su última salida (como se esperaba), pero seq 1 0.1 2da 1.9como la última salida (en lugar de la esperada 2).
Mark Dickinson
Por conveniencia, la sugerencia de @ Kos se puede implementar como itertools.takewhile(lambda x: (x+0.05)<1, itertools.count(0,0.1))o itertools.islice(itertools.count(0,0.1), 10)(después de que lo haya hecho import itertools), aunque no he probado cuál es más eficiente
anónimo el

Respuestas:

906

En lugar de usar un paso decimal directamente, es mucho más seguro expresar esto en términos de cuántos puntos desea. De lo contrario, es probable que un error de redondeo de punto flotante le dé un resultado incorrecto.

Puede usar la función linspace de la biblioteca NumPy (que no es parte de la biblioteca estándar pero es relativamente fácil de obtener). linspacetoma una cantidad de puntos para regresar y también le permite especificar si se incluye o no el punto final correcto:

>>> np.linspace(0,1,11)
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9,  1. ])
>>> np.linspace(0,1,10,endpoint=False)
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])

Si realmente desea utilizar un valor de paso de punto flotante, puede hacerlo con numpy.arange.

>>> import numpy as np
>>> np.arange(0.0, 1.0, 0.1)
array([ 0. ,  0.1,  0.2,  0.3,  0.4,  0.5,  0.6,  0.7,  0.8,  0.9])

Error de redondeo de punto flotante será causar problemas, sin embargo. Aquí hay un caso simple en el que el error de redondeo arangeproduce una matriz de longitud 4 cuando solo debería producir 3 números:

>>> numpy.arange(1, 1.3, 0.1)
array([1. , 1.1, 1.2, 1.3])
Andrew Jaffe
fuente
51
numpy es un componente tan omnipresente de python que considero que esta respuesta es la más 'pitónica' de todas.
ataque aéreo el
21
@AndreTerra El problema es que @ numpy @ es un paquete de terceros y agrega mucha sobrecarga en términos de gestión de dependencias, almacenamiento (para el paquete en sí), etc. Dependiendo de lo que esté haciendo el desarrollador, puede ser imposible de usar eso.
rbaleksandar
Disculpe, pero no entendí el error de redondeo de coma flotante en la última parte desde entonces np.linspace(1.,1.3,4)y np.arange(1.,1.3,0.1)di exactamente la misma salida
código muerto del
44
@deadcode La razón es que np.arange se define para producir un rango [start,stop)(es decir, excluir stop), por lo que no se esperaría que 1.3 se incluyera en la lista. Vea esta pregunta para saber por qué todavía está incluida y qué hacer contra ella.
dennis
55
La cantidad de un paquete utilizado no es un indicador de si es "Pythonic".
Alex Hall
213

El rango de Python () solo puede hacer enteros, no coma flotante. En su caso específico, puede utilizar una lista de comprensión en su lugar:

[x * 0.1 for x in range(0, 10)]

(Reemplace la llamada al rango con esa expresión).

Para el caso más general, es posible que desee escribir una función o generador personalizado.


fuente
36
Aún mejor, podría usar un generador de comprensión si está trabajando con Python 2.4+. (x * 0.1 for x in range(0, 10)).
JAB
42
Mejor aún , ponga en x/10lugar de x * 0.1: D Nada especial en realidad, pero algunos números allí serán más precisos, por ejemplo, para 3*0.1obtener 0.30000000000000004, mientras que para 3/10 obtendrá 0.3:)
Bloke
44
3/10 me da 0, no 0.3. 3 / 10.0 da 0.29999999999999999. Python 2.6.
19
@LarsWirzenius: en Python 2.2+, from __future__ import division; 3/10devuelve 0.3. Este comportamiento es el predeterminado en Python 3.x.
Benjamin Hodgson
2
La función round también se puede usar lst = [round (x * 0.10,2) para x en el rango (0,10)]
MARK
148

Partiendo de 'xrange ([inicio], stop [, paso])' , puede definir un generador que acepte y produzca cualquier tipo que elija (quédese con los tipos que admiten +y <):

>>> def drange(start, stop, step):
...     r = start
...     while r < stop:
...         yield r
...         r += step
...         
>>> i0=drange(0.0, 1.0, 0.1)
>>> ["%g" % x for x in i0]
['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1']
>>> 
gimel
fuente
45
Esto tiene problemas de redondeo. Mire aquí: code.activestate.com/recipes/66472
Christian Oudard
Lo extendería un poco para la otra dirección con a (mientras que r> stop) y un paso r - = correspondiente para dar la dirección opuesta.
user318904
1
Hice una función xfrange sin los problemas de precisión de flotación mencionados anteriormente. Compruébalo;) stackoverflow.com/questions/477486/…
Carlos Vega
1
Estás acumulando errores de redondeo. Utilice esto en su lugar: `i = 0; r = comenzar mientras r <parar: i + = 1; r = inicio + i * paso; rendimiento r`
Cees Timmerman
1
Esto es de pythoncentral.io/pythons-range-function-explained (y otras fuentes de documentación de Python)
Apostolos
31

Aumente la magnitud de ipara el bucle y luego reduzca cuando lo necesite.

for i * 100 in range(0, 100, 10):
    print i / 100.0

EDITAR: Sinceramente, no puedo recordar por qué pensé que eso funcionaría sintácticamente

for i in range(0, 11, 1):
    print i / 10.0

Eso debería tener el resultado deseado.

cmsjr
fuente
Creo que encontrará que range () funciona con enteros, en cuyo caso esta sería la única solución, utilizando al menos la misma función.
Matthew Scharley el
1
@cmsjr creative: D Solo una pequeña cosa: divida por 100.0 para evitar que Python trunca el resultado si está usando Python 2.x. Creo que en 3.0 funcionará como lo has codificado.
Dana el
2
for i * 100 in range(0, 100, 10): SyntaxError: no se puede asignar al operador
Anne van Rossum
25

scipytiene una función incorporada arangeque generaliza el range()constructor de Python para satisfacer sus requisitos de manejo de flotación.

from scipy import arange

Catherine Ray
fuente
8
Esto es exactamente lo mismo arangeque puedes encontrar en numpy: >>> import scipy >>> import numpy >>> numpy.arange is scipy.arangevolverá True.
iFreilicht
from nump import arange as range
musaraña
24

NumPy es un poco exagerado, creo.

[p/10 for p in range(0, 10)]
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

En términos generales, hacer un paso a paso 1/xpara yti

x=100
y=2
[p/x for p in range(0, int(x*y))]
[0.0, 0.01, 0.02, 0.03, ..., 1.97, 1.98, 1.99]

( 1/xprodujo menos ruido de redondeo cuando lo probé).

Kalle
fuente
18

Similar a la seq función de R , ésta devuelve una secuencia en cualquier orden dado el valor de paso correcto. El último valor es igual al valor de detención.

def seq(start, stop, step=1):
    n = int(round((stop - start)/float(step)))
    if n > 1:
        return([start + step*i for i in range(n+1)])
    elif n == 1:
        return([start])
    else:
        return([])

Resultados

seq(1, 5, 0.5)

[1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0]

seq(10, 0, -1)

[10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

seq(10, 0, -2)

[10, 8, 6, 4, 2, 0]

seq(1, 1)

[1]

zeferino
fuente
2
Esta es una gran respuesta para alguien que quiere obtenerla sin meterse demasiado en Python.
Chani
1
Eso era casi lo que estaba buscando, tenga en cuenta que seq(0.5, 3.0)vuelve [0.5, 1.5, 2.5, 3.5]. Para evitar que las últimas entradas estén fuera de rango, reemplace n = int(round(...con n = int(floor(...con la línea from math import flooren la parte superior (arriba def seq(...).
FriendFX
2
@FriendFX ¡No hagas esto! Si floorse usa, seq(0.2, 0.9, 0.1)no alcanzará el punto final derecho y volverá[0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7, 0.8]
fdermish en
@ user502144: Buena captura, gracias. Supongo que tengo que conformarme con una de las soluciones más complejas para mantenerla en general.
FriendFX
14

Me temo que la función incorporada range () devuelve una secuencia de valores enteros, por lo que no puede usarla para hacer un paso decimal.

Yo diría que solo use un bucle while:

i = 0.0
while i <= 1.0:
    print i
    i += 0.1

Si tiene curiosidad, Python está convirtiendo su 0.1 a 0, por lo que le dice que el argumento no puede ser cero.

Dana
fuente
2
¡No hagas esto! ¡Agregar .110 veces no es lo mismo que agregar 1! docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
cat
12

Aquí hay una solución usando itertools :

import itertools

def seq(start, end, step):
    if step == 0:
        raise ValueError("step must not be 0")
    sample_count = int(abs(end - start) / step)
    return itertools.islice(itertools.count(start, step), sample_count)

Ejemplo de uso:

for i in seq(0, 1, 0.1):
    print(i)
Pramod
fuente
En aras de la exhaustividad, debe calcular el valor absoluto de la variable sample_count, de esa manera su función también funcionará para un inicio negativo (es decir, de -10 a 10)
Deleteman
10
[x * 0.1 for x in range(0, 10)] 

en Python 2.7x le da el resultado de:

[0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]

pero si usas:

[ round(x * 0.1, 1) for x in range(0, 10)]

te da lo deseado:

[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]

Nik
fuente
5

Y si hace esto a menudo, es posible que desee guardar la lista generada r

r=map(lambda x: x/10.0,range(0,10))
for i in r:
    print i
RSabet
fuente
5

more_itertoolses una biblioteca de terceros que implementa una numeric_rangeherramienta:

import more_itertools as mit


for x in mit.numeric_range(0, 1, 0.1):
    print("{:.1f}".format(x))

Salida

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

Esta herramienta también funciona para Decimaly Fraction.

pylang
fuente
4

Mis versiones usan la función de rango original para crear índices multiplicativos para el turno. Esto permite la misma sintaxis a la función de rango original. Hice dos versiones, una con flotador y otra con decimal, porque descubrí que en algunos casos quería evitar la deriva de redondeo introducida por la aritmética de coma flotante.

Es coherente con los resultados del conjunto vacío como en rango / xrango.

Al pasar un solo valor numérico a cualquiera de las funciones, la salida del rango estándar devolverá el valor máximo del número entero del parámetro de entrada (por lo tanto, si le dio 5.5, devolvería el rango (6)).

Editar: el siguiente código ahora está disponible como paquete en pypi: Franges

## frange.py
from math import ceil
# find best range function available to version (2.7.x / 3.x.x)
try:
    _xrange = xrange
except NameError:
    _xrange = range

def frange(start, stop = None, step = 1):
    """frange generates a set of floating point values over the 
    range [start, stop) with step size step

    frange([start,] stop [, step ])"""

    if stop is None:
        for x in _xrange(int(ceil(start))):
            yield x
    else:
        # create a generator expression for the index values
        indices = (i for i in _xrange(0, int((stop-start)/step)))  
        # yield results
        for i in indices:
            yield start + step*i

## drange.py
import decimal
from math import ceil
# find best range function available to version (2.7.x / 3.x.x)
try:
    _xrange = xrange
except NameError:
    _xrange = range

def drange(start, stop = None, step = 1, precision = None):
    """drange generates a set of Decimal values over the
    range [start, stop) with step size step

    drange([start,] stop, [step [,precision]])"""

    if stop is None:
        for x in _xrange(int(ceil(start))):
            yield x
    else:
        # find precision
        if precision is not None:
            decimal.getcontext().prec = precision
        # convert values to decimals
        start = decimal.Decimal(start)
        stop = decimal.Decimal(stop)
        step = decimal.Decimal(step)
        # create a generator expression for the index values
        indices = (
            i for i in _xrange(
                0, 
                ((stop-start)/step).to_integral_value()
            )
        )  
        # yield results
        for i in indices:
            yield float(start + step*i)

## testranges.py
import frange
import drange
list(frange.frange(0, 2, 0.5)) # [0.0, 0.5, 1.0, 1.5]
list(drange.drange(0, 2, 0.5, precision = 6)) # [0.0, 0.5, 1.0, 1.5]
list(frange.frange(3)) # [0, 1, 2]
list(frange.frange(3.5)) # [0, 1, 2, 3]
list(frange.frange(0,10, -1)) # []
Nisan.H
fuente
¿Cómo puede frangefuncionar si la parada es None? Esa parte del código ya ni siquiera considera el tamaño del paso.
josch
1
@josch rangetiene dos firmas: range(stop)que asume un valor predeterminado start=0, step=1y range(start, stop, step)donde no se hacen suposiciones. frangerefleja eso. Cuando se utiliza la range(stop)firma, tanto frangey drangeempezar a 0 y el incremento en un 1, por lo que su comportamiento es idéntico al normal range(stop)comportamiento con tope redondeado al número entero más cercano.
Nisan.H
4

Esta es mi solución para obtener rangos con pasos flotantes.
Con esta función no es necesario importar numpy ni instalarlo.
Estoy bastante seguro de que podría mejorarse y optimizarse. Siéntase libre de hacerlo y publíquelo aquí.

from __future__ import division
from math import log

def xfrange(start, stop, step):

    old_start = start #backup this value

    digits = int(round(log(10000, 10)))+1 #get number of digits
    magnitude = 10**digits
    stop = int(magnitude * stop) #convert from 
    step = int(magnitude * step) #0.1 to 10 (e.g.)

    if start == 0:
        start = 10**(digits-1)
    else:
        start = 10**(digits)*start

    data = []   #create array

    #calc number of iterations
    end_loop = int((stop-start)//step)
    if old_start == 0:
        end_loop += 1

    acc = start

    for i in xrange(0, end_loop):
        data.append(acc/magnitude)
        acc += step

    return data

print xfrange(1, 2.1, 0.1)
print xfrange(0, 1.1, 0.1)
print xfrange(-1, 0.1, 0.1)

El resultado es:

[1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0]
[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1]
[-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0.0]
Carlos Vega
fuente
1
Hay un error al perder el último valor si está dentro de 1 paso del valor de detención. es decir, xfrange (1,10,2) solo hace 1,3,5,7, falta 9
Lóbulo
Para referencia y otros lectores, compare esta implementación con esta stackoverflow.com/a/477610/54964 . Esto no parece tener grandes problemas de flotación.
Léo Léopold Hertz 준영
@carlosvega ¿Puedes confirmar por qué Lobe obtiene su resultado?
Léo Léopold Hertz 준영
3

Para completar la boutique, una solución funcional:

def frange(a,b,s):
  return [] if s > 0 and a > b or s < 0 and a < b or s==0 else [a]+frange(a+s,b,s)
Bijou Trouvaille
fuente
2

Puedes usar esta función:

def frange(start,end,step):
    return map(lambda x: x*step, range(int(start*1./step),int(end*1./step)))
usuario376536
fuente
No parece funcionar correctamente, por ejemplolist(frange(99.8, 100.1, 0.1)) => [99.7, 99.80000000000001, 99.9]
Shai Coleman
2

El truco para evitar el problema de redondeo es usar un número separado para moverse a través del rango, que comienza y la mitad del paso anterior al inicio .

# floating point range
def frange(a, b, stp=1.0):
  i = a+stp/2.0
  while i<b:
    yield a
    a += stp
    i += stp

Alternativamente, numpy.arangese puede utilizar.

wolfram77
fuente
2

Se puede hacer usando la biblioteca Numpy. La función arange () permite pasos en flotante. Pero, devuelve una matriz numpy que se puede convertir a la lista usando tolist () para nuestra conveniencia.

for i in np.arange(0, 1, 0.1).tolist():
   print i
user3654478
fuente
2

Mi respuesta es similar a otras que usan map (), sin necesidad de NumPy, y sin usar lambda (aunque podría). Para obtener una lista de valores flotantes de 0.0 a t_max en pasos de dt:

def xdt(n):
    return dt*float(n)
tlist  = map(xdt, range(int(t_max/dt)+1))
Eric Myers
fuente
2

Nadie sorprendido ha mencionado aún la solución recomendada en los documentos de Python 3 :

Ver también:

  • La receta de linspace muestra cómo implementar una versión perezosa de rango adecuada para aplicaciones de coma flotante.

Una vez definida, la receta es fácil de usar y no requiere numpyni ninguna otra biblioteca externa, pero funciona como numpy.linspace(). Tenga en cuenta que, en lugar de un stepargumento, el tercer numargumento especifica el número de valores deseados, por ejemplo:

print(linspace(0, 10, 5))
# linspace(0, 10, 5)
print(list(linspace(0, 10, 5)))
# [0.0, 2.5, 5.0, 7.5, 10]

Cito una versión modificada de la receta completa de Python 3 de Andrew Barnert a continuación:

import collections.abc
import numbers

class linspace(collections.abc.Sequence):
    """linspace(start, stop, num) -> linspace object

    Return a virtual sequence of num numbers from start to stop (inclusive).

    If you need a half-open range, use linspace(start, stop, num+1)[:-1].
    """
    def __init__(self, start, stop, num):
        if not isinstance(num, numbers.Integral) or num <= 1:
            raise ValueError('num must be an integer > 1')
        self.start, self.stop, self.num = start, stop, num
        self.step = (stop-start)/(num-1)
    def __len__(self):
        return self.num
    def __getitem__(self, i):
        if isinstance(i, slice):
            return [self[x] for x in range(*i.indices(len(self)))]
        if i < 0:
            i = self.num + i
        if i >= self.num:
            raise IndexError('linspace object index out of range')
        if i == self.num-1:
            return self.stop
        return self.start + i*self.step
    def __repr__(self):
        return '{}({}, {}, {})'.format(type(self).__name__,
                                       self.start, self.stop, self.num)
    def __eq__(self, other):
        if not isinstance(other, linspace):
            return False
        return ((self.start, self.stop, self.num) ==
                (other.start, other.stop, other.num))
    def __ne__(self, other):
        return not self==other
    def __hash__(self):
        return hash((type(self), self.start, self.stop, self.num))
Chris_Rands
fuente
2

Para contrarrestar los problemas de precisión de flotación, puede usar el Decimalmódulo .

Esto exige un esfuerzo adicional de conversión Decimaldesde into floatmientras se escribe el código, pero en su lugar puede pasar stry modificar la función si ese tipo de conveniencia es realmente necesaria.

from decimal import Decimal
from decimal import Decimal as D


def decimal_range(*args):

    zero, one = Decimal('0'), Decimal('1')

    if len(args) == 1:
        start, stop, step = zero, args[0], one
    elif len(args) == 2:
        start, stop, step = args + (one,)
    elif len(args) == 3:
        start, stop, step = args
    else:
        raise ValueError('Expected 1 or 2 arguments, got %s' % len(args))

    if not all([type(arg) == Decimal for arg in (start, stop, step)]):
        raise ValueError('Arguments must be passed as <type: Decimal>')

    # neglect bad cases
    if (start == stop) or (start > stop and step >= zero) or \
                          (start < stop and step <= zero):
        return []

    current = start
    while abs(current) < abs(stop):
        yield current
        current += step

Resultados de muestra:

list(decimal_range(D('2')))
# [Decimal('0'), Decimal('1')]
list(decimal_range(D('2'), D('4.5')))
# [Decimal('2'), Decimal('3'), Decimal('4')]
list(decimal_range(D('2'), D('4.5'), D('0.5')))
# [Decimal('2'), Decimal('2.5'), Decimal('3.0'), Decimal('3.5'), Decimal('4.0')]
list(decimal_range(D('2'), D('4.5'), D('-0.5')))
# []
list(decimal_range(D('2'), D('-4.5'), D('-0.5')))
# [Decimal('2'),
#  Decimal('1.5'),
#  Decimal('1.0'),
#  Decimal('0.5'),
#  Decimal('0.0'),
#  Decimal('-0.5'),
#  Decimal('-1.0'),
#  Decimal('-1.5'),
#  Decimal('-2.0'),
#  Decimal('-2.5'),
#  Decimal('-3.0'),
#  Decimal('-3.5'),
#  Decimal('-4.0')]
shad0w_wa1k3r
fuente
2
Con Decimalentradas similares , np.arangefunciona igual:np.arange(Decimal('-2.0'), Decimal('2.0'), Decimal('0.1'))
hpaulj
2
Sí, gracias. Aunque, eso necesitaría una lib externa (numpy).
shad0w_wa1k3r
1

Agregue corrección automática para la posibilidad de un inicio de sesión incorrecto en el paso:

def frange(start,step,stop):
    step *= 2*((stop>start)^(step<0))-1
    return [start+i*step for i in range(int((stop-start)/step))]
BobH
fuente
1

Mi solución:

def seq(start, stop, step=1, digit=0):
    x = float(start)
    v = []
    while x <= stop:
        v.append(round(x,digit))
        x += step
    return v
Jjen
fuente
1

La mejor solución: sin error de redondeo
_________________________________________________________________________________

>>> step = .1
>>> N = 10     # number of data points
>>> [ x / pow(step, -1) for x in range(0, N + 1) ]

[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

_________________________________________________________________________________

O, para un rango establecido en lugar de puntos de datos establecidos (por ejemplo, función continua), use:

>>> step = .1
>>> rnge = 1     # NOTE range = 1, i.e. span of data points
>>> N = int(rnge / step
>>> [ x / pow(step,-1) for x in range(0, N + 1) ]

[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]

Para implementar una función: reemplace x / pow(step, -1)con f( x / pow(step, -1) )y defina f.
Por ejemplo:

>>> import math
>>> def f(x):
        return math.sin(x)

>>> step = .1
>>> rnge = 1     # NOTE range = 1, i.e. span of data points
>>> N = int(rnge / step)
>>> [ f( x / pow(step,-1) ) for x in range(0, N + 1) ]

[0.0, 0.09983341664682815, 0.19866933079506122, 0.29552020666133955, 0.3894183423086505, 
 0.479425538604203, 0.5646424733950354, 0.644217687237691, 0.7173560908995228,
 0.7833269096274834, 0.8414709848078965]
Jason
fuente
1

start y stop son inclusivos en lugar de uno u otro (generalmente se excluye stop) y sin importaciones, y utilizando generadores

def rangef(start, stop, step, fround=5):
    """
    Yields sequence of numbers from start (inclusive) to stop (inclusive)
    by step (increment) with rounding set to n digits.

    :param start: start of sequence
    :param stop: end of sequence
    :param step: int or float increment (e.g. 1 or 0.001)
    :param fround: float rounding, n decimal places
    :return:
    """
    try:
        i = 0
        while stop >= start and step > 0:
            if i==0:
                yield start
            elif start >= stop:
                yield stop
            elif start < stop:
                if start == 0:
                    yield 0
                if start != 0:
                    yield start
            i += 1
            start += step
            start = round(start, fround)
        else:
            pass
    except TypeError as e:
        yield "type-error({})".format(e)
    else:
        pass


# passing
print(list(rangef(-100.0,10.0,1)))
print(list(rangef(-100,0,0.5)))
print(list(rangef(-1,1,0.2)))
print(list(rangef(-1,1,0.1)))
print(list(rangef(-1,1,0.05)))
print(list(rangef(-1,1,0.02)))
print(list(rangef(-1,1,0.01)))
print(list(rangef(-1,1,0.005)))
# failing: type-error:
print(list(rangef("1","10","1")))
print(list(rangef(1,10,"1")))

Python 3.6.2 (v3.6.2: 5fd33b5, 8 de julio de 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)]

Goran B.
fuente
1

Sé que llego tarde a la fiesta aquí, pero aquí hay una solución trivial de generador que funciona en 3.6:

def floatRange(*args):
    start, step = 0, 1
    if len(args) == 1:
        stop = args[0]
    elif len(args) == 2:
        start, stop = args[0], args[1]
    elif len(args) == 3:
        start, stop, step = args[0], args[1], args[2]
    else:
        raise TypeError("floatRange accepts 1, 2, or 3 arguments. ({0} given)".format(len(args)))
    for num in start, step, stop:
        if not isinstance(num, (int, float)):
            raise TypeError("floatRange only accepts float and integer arguments. ({0} : {1} given)".format(type(num), str(num)))
    for x in range(int((stop-start)/step)):
        yield start + (x * step)
    return

entonces puede llamarlo como el original range()... no hay manejo de errores, pero avíseme si hay un error que pueda detectarse razonablemente, y lo actualizaré. o puedes actualizarlo. Este es StackOverflow.

David Culbreth
fuente
0

Aquí está mi solución que funciona bien con float_range (-1, 0, 0.01) y funciona sin errores de representación de coma flotante. No es muy rápido, pero funciona bien:

from decimal import Decimal

def get_multiplier(_from, _to, step):
    digits = []
    for number in [_from, _to, step]:
        pre = Decimal(str(number)) % 1
        digit = len(str(pre)) - 2
        digits.append(digit)
    max_digits = max(digits)
    return float(10 ** (max_digits))


def float_range(_from, _to, step, include=False):
    """Generates a range list of floating point values over the Range [start, stop]
       with step size step
       include=True - allows to include right value to if possible
       !! Works fine with floating point representation !!
    """
    mult = get_multiplier(_from, _to, step)
    # print mult
    int_from = int(round(_from * mult))
    int_to = int(round(_to * mult))
    int_step = int(round(step * mult))
    # print int_from,int_to,int_step
    if include:
        result = range(int_from, int_to + int_step, int_step)
        result = [r for r in result if r <= int_to]
    else:
        result = range(int_from, int_to, int_step)
    # print result
    float_result = [r / mult for r in result]
    return float_result


print float_range(-1, 0, 0.01,include=False)

assert float_range(1.01, 2.06, 5.05 % 1, True) ==\
[1.01, 1.06, 1.11, 1.16, 1.21, 1.26, 1.31, 1.36, 1.41, 1.46, 1.51, 1.56, 1.61, 1.66, 1.71, 1.76, 1.81, 1.86, 1.91, 1.96, 2.01, 2.06]

assert float_range(1.01, 2.06, 5.05 % 1, False)==\
[1.01, 1.06, 1.11, 1.16, 1.21, 1.26, 1.31, 1.36, 1.41, 1.46, 1.51, 1.56, 1.61, 1.66, 1.71, 1.76, 1.81, 1.86, 1.91, 1.96, 2.01]
pymen
fuente
0

Solo soy un principiante, pero tuve el mismo problema al simular algunos cálculos. Así es como intenté resolver esto, que parece estar funcionando con pasos decimales.

También soy bastante vago, así que me resultó difícil escribir mi propia función de rango.

Básicamente lo que hice se me cambió xrange(0.0, 1.0, 0.01)a xrange(0, 100, 1)y se utiliza la división por 100.0dentro del bucle. También me preocupaba si habría errores de redondeo. Así que decidí probar, si hay alguno. Ahora escuché que si, por ejemplo, 0.01de un cálculo no es exactamente el flotador0.01 compara, debería devolver False (si estoy equivocado, hágamelo saber).

Así que decidí probar si mi solución funcionaría para mi rango ejecutando una prueba corta:

for d100 in xrange(0, 100, 1):
    d = d100 / 100.0
    fl = float("0.00"[:4 - len(str(d100))] + str(d100))
    print d, "=", fl , d == fl

E imprimió True para cada uno.

Ahora, si me estoy equivocando totalmente, hágamelo saber.

usuario2836437
fuente
0

Este único trazador de líneas no saturará su código. El signo del parámetro paso es importante.

def frange(start, stop, step):
    return [x*step+start for x in range(0,round(abs((stop-start)/step)+0.5001),
        int((stop-start)/step<0)*-2+1)]
Tjaart
fuente