How do I use a decimal step value for range()?
Asked Answered
S

34

1011

How do I iterate between 0 and 1 by a step of 0.1?

This says that the step argument cannot be zero:

for i in range(0, 1, 0.1):
    print(i)
Shimmer answered 25/1, 2009 at 10:20 Comment(5)
int(0.1) == 0, so the step actually is zero. It may be unexpected, but it is zero. You might want to restate your question to reflect that fact that it's you didn't expect this. Saying "it's not" is false and misleading.Aintab
BTW A short one-liner can be rolled up using itertools.takewhile and itertools.count. It isn't better than drange performance-wise, though.Cassiecassil
It's embarrassing that python's range dosen't allow this, given how easy it is to implement a generator that does this even without accumulating rounding errors. Heck, even the seq tool in GNU coreutils allows one to do seq 0 0.1 1 without rounding errors!Godgiven
@josch: seq uses the C long double type internally, and is subject to rounding errors. For example on my machine, seq 0 0.1 1 gives 1 as its last output (as expected), but seq 1 0.1 2 gives 1.9 as the last output (rather than the expected 2).Aho
For convenience, @Kos's suggestion can be implemented as itertools.takewhile(lambda x: (x+0.05)<1, itertools.count(0,0.1)) or itertools.islice(itertools.count(0,0.1), 10) (after you have import itertools), though I haven't tested which is more efficientCentenarian
I
1223

Rather than using a decimal step directly, it's much safer to express this in terms of how many points you want. Otherwise, floating-point rounding error is likely to give you a wrong result.

Use the linspace function from the NumPy library (which isn't part of the standard library but is relatively easy to obtain). linspace takes a number of points to return, and also lets you specify whether or not to include the right endpoint:

>>> 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])

If you really want to use a floating-point step value, use 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])

Floating-point rounding error will cause problems, though. Here's a simple case where rounding error causes arange to produce a length-4 array when it should only produce 3 numbers:

>>> numpy.arange(1, 1.3, 0.1)
array([1. , 1.1, 1.2, 1.3])
Indisposed answered 25/1, 2009 at 12:26 Comment(8)
numpy is such an ubiquitous component of python that I consider this answer to be the most 'pythonic' of all.Sleigh
@AndreTerra The problem is that @numpy@ is a third party package and adds a lot of overhead in terms of dependency-management, storage (for the package itself) etc. Depending on what the developer is doing, it may be impossible to use it.Berkeleian
Pardon me, but I didn't understand the floating point rounding error in the last part since np.linspace(1.,1.3,4) and np.arange(1.,1.3,0.1) give exactly the same outputMerkley
@Merkley The reason is that np.arange is defined to produce a range [start,stop) (i.e. excluding stop), so one would not expect 1.3 to be included in the list. See this question for why it is still included and what to do against it.Standley
How much a package is used is arguably not any indicator of whether it is "Pythonic."Ahriman
I found a case where np.linespace gave a floating point on 0, which can be fixed with [round(i, 1) for i in np.linspace(-0.8, 2.0, 29)]Transact
what causes list(np.linspace(0,1,11)) to return some floating rounding errors like 0.30000000000004? is it list?Contractual
Came to say the same as prev comment, using linspace does result in floating point rounding errors too. IMO, since python is garbage collected, the 'safety' gained from creating a list of the exact number of items is negligible. Sometimes you don't even know exactly how many points you'll need, you just want to arbitrarily input lower and upper bounds and get an iterable with specific step between the 2 bounds. Idk, if it's the best approach possible, but user25148 answer seems better to meAslam
V
298

range() can only do integers, not floating point.

Use a list comprehension instead to obtain a list of steps:

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

More generally, a generator comprehension minimizes memory allocations:

xs = (x * 0.1 for x in range(0, 10))
for x in xs:
    print(x)
Volitant answered 25/1, 2009 at 10:35 Comment(8)
Even better, you could just use a generator comprehension if you're working with Python 2.4+. (x * 0.1 for x in range(0, 10)).Malm
Even better, put x/10 instead of x * 0.1 :D Nothing special actually, but some numbers in there will be more precise, e.g. for 3*0.1 you get 0.30000000000000004, while for 3/10 you get 0.3 :)Amaryllidaceous
3/10 gives me 0, not 0.3. 3/10.0 gives 0.29999999999999999. Python 2.6.Volitant
@LarsWirzenius: in Python 2.2+, from __future__ import division; 3/10 returns 0.3. This behaviour is the default in Python 3.x.Brython
round function can also be used lst = [round(x* 0.10,2) for x in range(0,10)]Claudine
@user25148: If at least one of the division operands is a float, the result in Python 2.x will also be a float. So you could do x / 10.0.Excruciation
@Amaryllidaceous D-smileys don't fit StackOverflow.Dragrope
On Windows 10, with Python 3.8.7, [x * 0.1 for x in range(0, 10)] gave me some funky output: [0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9]. I used @Amaryllidaceous suggestion of using x / 10 and received the proper output: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]Salomone
A
159

Building on 'xrange([start], stop[, step])', you can define a generator that accepts and produces any type you choose (stick to types supporting + and <):

>>> 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']
>>> 
Arabinose answered 25/1, 2009 at 11:57 Comment(6)
This has roundoff problems. Please look here: code.activestate.com/recipes/66472Castellan
I would extend it a bit for the other direction with a (while r > stop) and a corresponding r -= step for giving the opposite direction.Overmatter
yours for: l=frange(0,1,0.1) print l returns: [0.0, 0.1, 0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7000000000000001, 0.8, 0.9] while numpy: [ 0. 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9]Reeder
I did a xfrange function without the float precision problems referred above. Check it out ;) #477986Switzerland
You're accumulating rounding errors. Please use this instead: ` i = 0; r = start while r < stop: i += 1; r = start + i * step; yield r`Degreeday
This is from pythoncentral.io/pythons-range-function-explained (and other Python documentation sources)Shiner
L
38

Increase the magnitude of i for the loop and then reduce it when you need it.

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

EDIT: I honestly cannot remember why I thought that would work syntactically

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

That should have the desired output.

Luciano answered 25/1, 2009 at 10:32 Comment(3)
I think you'll find that range() works off integers, in which case this would be the only solution, using the same function atleast.Connie
@Luciano creative :D Just one little thing: divide by 100.0 to keep Python from truncating the result if you're using Python 2.x. I think in 3.0, it'll work as you've coded it.Catastrophe
for i * 100 in range(0, 100, 10): SyntaxError: can't assign to operatorSystematic
C
36

NumPy is a bit overkill, I think.

[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]

Generally speaking, to do a step-by-1/x up to y you would do

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/x produced less rounding noise when I tested).

Crispa answered 28/2, 2017 at 2:6 Comment(1)
Cleanest solution imo. List comprehension also makes it look very short and simple.Cerography
R
24

scipy has a built in function arange which generalizes Python's range() constructor to satisfy your requirement of float handling.

from scipy import arange

Resinoid answered 17/5, 2014 at 20:50 Comment(1)
This is actually the exact same arange you can find in numpy: >>> import scipy >>> import numpy >>> numpy.arange is scipy.arange will return True.Storiette
N
21

Similar to R's seq function, this one returns a sequence in any order given the correct step value. The last value is equal to the stop value.

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([])

Results

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 ]

Novgorod answered 11/6, 2012 at 19:10 Comment(4)
This is a great answer for someone who wants to get it one without getting too much into python.Spain
That was almost what I was looking for - note that seq(0.5, 3.0) returns [0.5, 1.5, 2.5, 3.5]. To avoid last entries being out-of-range, replace n = int(round(... with n = int(floor(... with the line from math import floor at the top (above def seq(...).Friede
@Friede Don't do this! If floor is used, seq(0.2, 0.9, 0.1) will fail to reach right endpoint and will return [0.2, 0.30000000000000004, 0.4, 0.5, 0.6000000000000001, 0.7, 0.8]Undercut
@user502144: Nice catch, thanks. I guess I have to settle for one of the more complex solutions in order to keep it general.Friede
C
17

The range() built-in function returns a sequence of integer values, I'm afraid, so you can't use it to do a decimal step.

I'd say just use a while loop:

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

If you're curious, Python is converting your 0.1 to 0, which is why it's telling you the argument can't be zero.

Catastrophe answered 25/1, 2009 at 10:32 Comment(1)
Don't do this! Adding .1 10 times is not the same as adding 1! docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.htmlBranscum
D
15

Here's a solution using 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)

Usage Example:

for i in seq(0, 1, 0.1):
    print(i)
Danuloff answered 8/11, 2012 at 10:0 Comment(1)
For the sake of completeness, you should calculate the absolute value for the sample_count variable, that way your function will also work for a negative start (i.e from -10 to 10)Aluminize
M
12
[x * 0.1 for x in range(0, 10)] 

in Python 2.7x gives you the result of:

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

but if you use:

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

gives you the desired:

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

Malignancy answered 13/5, 2012 at 23:20 Comment(0)
D
10
import numpy as np
for i in np.arange(0, 1, 0.1): 
    print i 
Delphinedelphinia answered 7/9, 2012 at 2:1 Comment(1)
this has the rounding issue, quote: "When using a non-integer step, such as 0.1, the results will often not be consistent." -- docs.scipy.org/doc/numpy/reference/generated/numpy.arange.htmlSapphira
B
7

Best Solution: no rounding error

>>> 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]

Or, for a set range instead of set data points (e.g. continuous function), 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]

To implement a function: replace x / pow(step, -1) with f( x / pow(step, -1) ), and define f.
For example:

>>> 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]
Bab answered 10/12, 2016 at 19:3 Comment(0)
S
6

And if you do this often, you might want to save the generated list r

r=map(lambda x: x/10.0,range(0,10))
for i in r:
    print i
Soninlaw answered 25/1, 2009 at 10:48 Comment(0)
W
6

more_itertools is a third-party library that implements a numeric_range tool:

import more_itertools as mit


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

Output

0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9

This tool also works for Decimal and Fraction.

Warmup answered 9/2, 2018 at 0:31 Comment(0)
G
5

My versions use the original range function to create multiplicative indices for the shift. This allows same syntax to the original range function. I have made two versions, one using float, and one using Decimal, because I found that in some cases I wanted to avoid the roundoff drift introduced by the floating point arithmetic.

It is consistent with empty set results as in range/xrange.

Passing only a single numeric value to either function will return the standard range output to the integer ceiling value of the input parameter (so if you gave it 5.5, it would return range(6).)

Edit: the code below is now available as package on 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)) # []
Gravois answered 12/12, 2012 at 22:29 Comment(2)
How can frange work if stop is None? That part of the code doesn't even consider the step size anymore.Godgiven
@Godgiven range has two signatures: range(stop), which assumes a default start=0, step=1, and range(start, stop, step), where no assumptions are made. frange reflects that. When using the range(stop) signature, both frange and drange start at 0 and increment by 1, so their behaviour is identical to regular range(stop) behaviour with stop rounded up to the nearest integer.Gravois
R
5

Suprised no-one has yet mentioned the recommended solution in the Python 3 docs:

See also:

  • The linspace recipe shows how to implement a lazy version of range that suitable for floating point applications.

Once defined, the recipe is easy to use and does not require numpy or any other external libraries, but functions like numpy.linspace(). Note that rather than a step argument, the third num argument specifies the number of desired values, for example:

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

I quote a modified version of the full Python 3 recipe from Andrew Barnert below:

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))
Roose answered 2/3, 2018 at 15:0 Comment(0)
T
5

Lots of the solutions here still had floating point errors in Python 3.6 and didnt do exactly what I personally needed.

Function below takes integers or floats, doesnt require imports and doesnt return floating point errors.

def frange(x, y, step):
    if int(x + y + step) == (x + y + step):
        r = list(range(int(x), int(y), int(step)))
    else:
        f = 10 ** (len(str(step)) - str(step).find('.') - 1)
        rf = list(range(int(x * f), int(y * f), int(step * f)))
        r = [i / f for i in rf]

    return r
Tereasaterebene answered 5/2, 2019 at 18:49 Comment(3)
Nice solution of v3.9 as well. Lack of imports is good. CheersLuana
If wanting the output list to be inclusive of the entire range, change to rf = list(range(int(x * f), int((y+step) * f), int(step * f)))Luana
This is so good, except if you can make frange(end, start=0, step=1) and it will work similar to rangeHeliozoan
S
3

This is my solution to get ranges with float steps.
Using this function it's not necessary to import numpy, nor install it.
I'm pretty sure that it could be improved and optimized. Feel free to do it and post it here.

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)

The output is:

[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]
Switzerland answered 12/12, 2013 at 17:4 Comment(3)
There is an error with missing the last value if it is within 1 step of the stop value. i.e. xfrange(1,10,2) only does 1,3,5,7, missing 9Hissing
For reference and other readers, please compare this implementation to this https://mcmap.net/q/53195/-how-do-i-use-a-decimal-step-value-for-range. This does not seem to have big float problems.Rosierosily
@carlosvega Can you confirm why Lobe gets his result?Rosierosily
N
3

For completeness of boutique, a functional solution:

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)
Nine answered 12/9, 2015 at 7:44 Comment(0)
H
2

You can use this function:

def frange(start,end,step):
    return map(lambda x: x*step, range(int(start*1./step),int(end*1./step)))
Helle answered 25/6, 2010 at 17:57 Comment(3)
Doesn't seem to work correctly, e.g. list(frange(99.8, 100.1, 0.1)) => [99.7, 99.80000000000001, 99.9]Carmelocarmen
@ShaiColeman That's general floating point rounding, not a flaw of this particular method. If you worry about this, several answers here contain workarounds; see perhaps also #588504Ductile
@Ductile , It's wrong even ignoring the rounding errors. expected: [99.8, 99.9, 100.0] actual: [99.7, 99.8, 99.9]Carmelocarmen
S
2

It can be done using Numpy library. arange() function allows steps in float. But, it returns a numpy array which can be converted to list using tolist() for our convenience.

for i in np.arange(0, 1, 0.1).tolist():
   print i
Shouse answered 12/2, 2016 at 12:18 Comment(0)
I
2

start and stop are inclusive rather than one or the other (usually stop is excluded) and without imports, and using generators

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, Jul 8 2017, 04:57:36) [MSC v.1900 64 bit (AMD64)]

Iddo answered 1/3, 2018 at 21:53 Comment(0)
T
2

To counter the float precision issues, you could use the Decimal module.

This demands an extra effort of converting to Decimal from int or float while writing the code, but you can instead pass str and modify the function if that sort of convenience is indeed necessary.

from decimal import Decimal


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

Sample outputs -

from decimal import Decimal as D

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')]
Tyndall answered 4/3, 2018 at 13:5 Comment(5)
WIth similar Decimal inputs, np.arange works the same: np.arange(Decimal('-2.0'), Decimal('2.0'), Decimal('0.1'))Gifu
Yep, thanks. Although, that would need an external (numpy) lib.Tyndall
I'd appreciate if you can provide feedback or reason for the downvote.Tyndall
Questions about downvotes are pointless, since voters aren't notified, and hance rarely see them. I was notified based on a 2 year old comment.Gifu
Sorry to ping you, hoped it won't since I didn't tag. And yeah, my comment was just hopeful.Tyndall
V
2

I know I'm late to the party here, but here's a trivial generator solution that's working in 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

then you can call it just like the original range()... there's no error handling, but let me know if there is an error that can be reasonably caught, and I'll update. or you can update it. this is StackOverflow.

Vinni answered 2/1, 2019 at 21:7 Comment(1)
As a warning, this solution doesn't implement the __contains__ operator, and depending on your use case, it could be very VERY slow to call if x in list(floatRange(a,b,c)):...Vinni
S
1

Add auto-correction for the possibility of an incorrect sign on step:

def frange(start,step,stop):
    step *= 2*((stop>start)^(step<0))-1
    return [start+i*step for i in range(int((stop-start)/step))]
Spitzer answered 1/12, 2010 at 6:36 Comment(0)
K
1

My solution:

def seq(start, stop, step=1, digit=0):
    x = float(start)
    v = []
    while x <= stop:
        v.append(round(x,digit))
        x += step
    return v
Kuska answered 25/7, 2012 at 13:50 Comment(0)
P
1

Here is my solution which works fine with float_range(-1, 0, 0.01) and works without floating point representation errors. It is not very fast, but works fine:

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]
Polky answered 8/1, 2013 at 21:21 Comment(0)
F
1

I am only a beginner, but I had the same problem, when simulating some calculations. Here is how I attempted to work this out, which seems to be working with decimal steps.

I am also quite lazy and so I found it hard to write my own range function.

Basically what I did is changed my xrange(0.0, 1.0, 0.01) to xrange(0, 100, 1) and used the division by 100.0 inside the loop. I was also concerned, if there will be rounding mistakes. So I decided to test, whether there are any. Now I heard, that if for example 0.01 from a calculation isn't exactly the float 0.01 comparing them should return False (if I am wrong, please let me know).

So I decided to test if my solution will work for my range by running a short test:

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

And it printed True for each.

Now, if I'm getting it totally wrong, please let me know.

Far answered 10/10, 2013 at 22:27 Comment(0)
W
1

The trick to avoid round-off problem is to use a separate number to move through the range, that starts and half the step ahead of start.

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

Alternatively, numpy.arange can be used.

Werner answered 2/9, 2015 at 7:28 Comment(0)
V
1

My answer is similar to others using map(), without need of NumPy, and without using lambda (though you could). To get a list of float values from 0.0 to t_max in steps of dt:

def xdt(n):
    return dt*float(n)
tlist  = map(xdt, range(int(t_max/dt)+1))
Voracity answered 16/2, 2017 at 19:48 Comment(0)
M
1
f = lambda x,y,z: (x+i*z for i in range(int((y-x)/z)))

Above can do fractional step without resorting to any library.

Multinuclear answered 3/1, 2022 at 13:36 Comment(0)
L
0

This one liner will not clutter your code. The sign of the step parameter is important.

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)]
Lice answered 20/11, 2013 at 16:47 Comment(0)
W
0

frange(start, stop, precision)

def frange(a,b,i):
    p = 10**i
    sr = a*p
    er = (b*p) + 1
    p = float(p)
    return map(lambda x: x/p, xrange(sr,er))

In >frange(-1,1,1)

Out>[-1.0, -0.9, -0.8, -0.7, -0.6, -0.5, -0.4, -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
Woodhead answered 10/7, 2014 at 18:15 Comment(0)
H
0
sign = lambda x: (1, -1)[x < 0]
def frange(start, stop, step):
    i = 0
    r=len(str(step).split('.')[-1])
    args=(start,stop,step)
    if not step :return []
    if all(int(i)==float(i) for i in args):
        start,stop,step=map(int,args)
    if sign(step)==1:
        while start + i * step < stop:
            yield round(start + i * step,r)
            i += 1
    else:
        while start + i * step > stop:
            yield round(start + i * step,r)
            i += 1
Hamper answered 5/6, 2018 at 7:58 Comment(1)
It's good practice on Stack Overflow to add an explanation as to why your solution should work. For more information read How To Answer.Substitute

© 2022 - 2024 — McMap. All rights reserved.