852

Is there a way to step between 0 and 1 by 0.1?

I thought I could do it like the following, but it failed:

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

Instead, it says that the step argument cannot be zero, which I did not expect.

martineau
  • 99,260
  • 22
  • 139
  • 249
Evan Fosmark
  • 89,147
  • 33
  • 99
  • 116
  • 18
    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. – S.Lott Jan 25 '09 at 13:34
  • 3
    BTW A short one-liner can be rolled up using `itertools.takewhile` and `itertools.count`. It isn't better than `drange` performance-wise, though. – Kos Nov 29 '12 at 16:15
  • 2
    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! – josch Aug 20 '16 at 05:55
  • 3
    @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`). – Mark Dickinson Oct 24 '17 at 18:39
  • 1
    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 efficient – Anonymous Sep 19 '19 at 14:13

33 Answers33

1036

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.

You can 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, you can, with 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])
Brian Burns
  • 14,953
  • 5
  • 69
  • 59
Andrew Jaffe
  • 23,727
  • 2
  • 45
  • 55
  • 59
    numpy is such an ubiquitous component of python that I consider this answer to be the most 'pythonic' of all. – airstrike Sep 11 '13 at 19:20
  • 25
    @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. – rbaleksandar May 24 '17 at 13:56
  • 2
    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 output – deadcode Oct 12 '18 at 21:09
  • 5
    @deadcode The reason is that [np.arange](https://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html) 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](https://stackoverflow.com/questions/48755701/how-to-prevent-float-imprecision-from-affecting-numpy-arange) for why it is still included and what to do against it. – dennis Nov 16 '18 at 12:48
  • 7
    How much a package is used is arguably not any indicator of whether it is "Pythonic." – Alex Hall Feb 11 '19 at 00:59
  • 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)]` – Wychh Nov 30 '20 at 12:50
239

Python's range() can only do integers, not floating point. In your specific case, you can use a list comprehension instead:

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

(Replace the call to range with that expression.)

For the more general case, you may want to write a custom function or generator.

  • 39
    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))`. – JAB Jun 25 '10 at 18:02
  • 53
    _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` :) – Bloke May 26 '12 at 18:22
  • 6
    3/10 gives me 0, not 0.3. 3/10.0 gives 0.29999999999999999. Python 2.6. –  May 27 '12 at 08:19
  • 22
    @LarsWirzenius: in Python 2.2+, `from __future__ import division; 3/10` returns 0.3. This behaviour is the default in Python 3.x. – Benjamin Hodgson Sep 14 '12 at 11:15
  • 2
    round function can also be used lst = [round(x* 0.10,2) for x in range(0,10)] – MARK Jan 30 '14 at 08:34
  • 1
    @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`. – Magnus Teekivi Nov 28 '17 at 11:06
  • 1
    @Bloke D-smileys don't fit StackOverflow. – Det Jan 31 '20 at 22:24
  • 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 @Bloke 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]` – howdoicode Feb 14 '21 at 15:23
153

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']
>>> 
gimel
  • 73,814
  • 10
  • 69
  • 104
  • 48
    This has roundoff problems. Please look here: http://code.activestate.com/recipes/66472/ – Christian Oudard Oct 12 '09 at 20:50
  • 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. – user318904 Nov 08 '10 at 03:59
  • 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]` – andilabs Sep 05 '13 at 14:20
  • 1
    I did a xfrange function without the float precision problems referred above. Check it out ;) http://stackoverflow.com/questions/477486/python-decimal-range-step-value/20549652#20549652 – Carlos Vega Dec 12 '13 at 17:05
  • 1
    You're accumulating rounding errors. Please use this instead: ` i = 0; r = start while r < stop: i += 1; r = start + i * step; yield r` – Cees Timmerman Apr 11 '16 at 11:56
  • 1
    This is from https://www.pythoncentral.io/pythons-range-function-explained/ (and other Python documentation sources) – Apostolos Jan 05 '18 at 17:31
32

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.

cmsjr
  • 49,560
  • 10
  • 66
  • 62
  • 1
    I think you'll find that range() works off integers, in which case this would be the only solution, using the same function atleast. – Matthew Scharley Jan 25 '09 at 10:33
  • 2
    @cmsjr 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. – Dana Jan 25 '09 at 10:35
  • 4
    `for i * 100 in range(0, 100, 10)`: SyntaxError: can't assign to operator – Anne van Rossum Jan 06 '15 at 15:02
30

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

Kalle
  • 12,846
  • 7
  • 58
  • 74
23

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

from scipy import arange

Catherine Ray
  • 347
  • 3
  • 4
  • 13
    This is actually the exact same `arange` you can find in numpy: `>>> import scipy >>> import numpy >>> numpy.arange is scipy.arange` will return `True`. – iFreilicht Jul 10 '17 at 11:17
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 ]

zeferino
  • 480
  • 1
  • 5
  • 10
  • 2
    This is a great answer for someone who wants to get it one without getting too much into python. – Chani Aug 03 '14 at 11:40
  • 1
    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(...`). – FriendFX Nov 09 '15 at 04:44
  • 3
    @FriendFX 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]` – fdermishin Jan 25 '16 at 11:58
  • @user502144: Nice catch, thanks. I guess I have to settle for one of the more complex solutions in order to keep it general. – FriendFX Jan 27 '16 at 02:33
14

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.

Dana
  • 28,425
  • 17
  • 58
  • 72
  • 2
    Don't do this! Adding `.1` 10 times is not the same as adding `1`! https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html – cat Mar 14 '16 at 23:51
13

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)
Pramod
  • 4,860
  • 3
  • 40
  • 43
  • 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) – Deleteman Mar 18 '15 at 12:31
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]

Liam McInroy
  • 4,144
  • 4
  • 30
  • 52
Nik
  • 151
  • 1
  • 3
8
import numpy as np
for i in np.arange(0, 1, 0.1): 
    print i 
Steve Czetty
  • 5,972
  • 8
  • 37
  • 48
Raja
  • 97
  • 1
  • 1
  • 1
    this has the rounding issue, quote: "When using a non-integer step, such as 0.1, the results will often not be consistent." -- https://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html – galactica Mar 01 '19 at 19:55
5

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
RSabet
  • 5,626
  • 3
  • 25
  • 26
4

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)) # []
Nisan.H
  • 5,334
  • 2
  • 19
  • 25
  • How can `frange` work if stop is `None`? That part of the code doesn't even consider the step size anymore. – josch Aug 20 '16 at 06:26
  • 1
    @josch `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. – Nisan.H Aug 24 '16 at 22:09
4

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]
tripleee
  • 139,311
  • 24
  • 207
  • 268
Jason
  • 1,431
  • 17
  • 17
4

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.

pylang
  • 28,402
  • 9
  • 97
  • 94
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]
Carlos Vega
  • 1,232
  • 2
  • 12
  • 32
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)
Bijou Trouvaille
  • 6,775
  • 4
  • 34
  • 41
3

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))
Chris_Rands
  • 30,797
  • 12
  • 66
  • 100
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)))
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
user376536
  • 21
  • 1
  • Doesn't seem to work correctly, e.g. `list(frange(99.8, 100.1, 0.1)) => [99.7, 99.80000000000001, 99.9]` – Shai Coleman Jul 29 '18 at 19:16
  • @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 https://stackoverflow.com/questions/588004/is-floating-point-math-broken – tripleee Jul 02 '20 at 08:38
  • @tripleee , It's wrong even ignoring the rounding errors. expected: `[99.8, 99.9, 100.0]` actual: `[99.7, 99.8, 99.9]` – Shai Coleman Oct 15 '20 at 12:05
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)]

Goran B.
  • 512
  • 4
  • 11
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')]
shad0w_wa1k3r
  • 11,346
  • 8
  • 59
  • 80
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.

David Culbreth
  • 1,792
  • 15
  • 22
  • 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)):...` – David Culbreth Jan 12 '21 at 17:47
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
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
Jjen
  • 11
  • 1
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]
pymen
  • 3,591
  • 34
  • 27
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.

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.

wolfram77
  • 1,751
  • 1
  • 17
  • 26
1

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
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))
Eric Myers
  • 39
  • 2
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))]
BobH
  • 11
  • 1
1

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
kowpow
  • 43
  • 8
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)]
Tjaart
  • 396
  • 6
  • 19
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]
zred
  • 1
-1
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
Smart Manoj
  • 3,837
  • 2
  • 24
  • 45
  • 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](//stackoverflow.com/help/how-to-answer). – Samuel Liew Jun 05 '18 at 11:43