51

The documentation basically says that range must behave exactly as this implementation (for positive step):

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

It also says that its arguments must be integers. Why is that? Isn't that definition also perfectly valid if step is a float?

In my case, I am esp. needing a range function which accepts a float type as its step argument. Is there any in Python or do I need to implement my own?


More specific: How would I translate this C code directly to Python in a nice way (i.e. not just doing it via a while-loop manually):

for(float x = 0; x < 10; x += 0.5f) { /* ... */ }
Bach
  • 6,145
  • 7
  • 36
  • 61
Albert
  • 65,406
  • 61
  • 242
  • 386

8 Answers8

53

You could use numpy.arange.

EDIT: The docs prefer numpy.linspace. Thanks @Droogans for noticing =)

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • 3
    The _first paragraph_ states: When using a non-integer step, such as 0.1, the results will often not be consistent. It is better to use [`linspace`](http://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html?highlight=linspace#numpy.linspace) for these cases. – yurisich Oct 15 '11 at 01:42
  • 2
    Note that `linspace` has different interface. – Beni Cherniavsky-Paskin Jul 11 '12 at 16:25
  • Or even without numpy.arange. I did a xfrange function without the float precision problems. Check it out ;) http://stackoverflow.com/questions/477486/python-decimal-range-step-value/20549652#20549652 – Carlos Vega Dec 12 '13 at 17:07
36

One explanation might be floating point rounding issues. For example, if you could call

range(0, 0.4, 0.1)

you might expect an output of

[0, 0.1, 0.2, 0.3]

but you in fact get something like

[0, 0.1, 0.2000000001, 0.3000000001]

due to rounding issues. And since range is often used to generate indices of some sort, it's integers only.

Still, if you want a range generator for floats, you can just roll your own.

def xfrange(start, stop, step):
    i = 0
    while start + i * step < stop:
        yield start + i * step
        i += 1
Zarkonnen
  • 22,200
  • 14
  • 65
  • 81
  • Yes of course (it's just natural that it behaves that way). That is always the case with floats. So of course it would totally be legitim that if `range` would support floats, it would behave that way. So I don't really get the point why it shouldn't. – Albert Nov 15 '10 at 23:20
  • Yeah, you're right. The best I can come up with is "it's imprecise". And "maybe it's made necessary by some common implementation detail?" – Zarkonnen Nov 15 '10 at 23:24
  • 1
    @Albert: there's usually not much point wondering why decisions like this were made; they just were. In this case the limited use cases would be outwe4ighed by thepotential for nasty bugs imho. – Katriel Nov 15 '10 at 23:30
  • 2
    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:50
  • Why did this get upvoted so much? Please consider the comment of @CeesTimmerman and incorporate it into your answer for a solution that does not accumulate rounding errors. – josch Aug 19 '16 at 08:56
  • 1
    @josch I apparently missed Cees' comment. And you're absolutely right of course, my version would have accumulated rounding errors like crazy. I'm a little disturbed revisiting it, in fact, because it's so obviously the wrong thing to do! But it's now - finally - fixed. Thank you for prodding me about it. – Zarkonnen Aug 23 '16 at 13:07
  • 1
    It is broken for negative `step`, fix your `while` condition like so: `while (start + i * step) * (-1 if step < 0 else 1) < stop:` – user443854 Jan 24 '19 at 19:43
13

In order to be able to use decimal numbers in a range expression a cool way for doing it is the following: [x * 0.1 for x in range(0, 10)]

mariana soffer
  • 1,853
  • 12
  • 17
  • 3
    If your "range" has a lot of numbers and your "step" of 0.1 is smaller, say .00000001, then this won't work and the script will just hang on most computers. We will need something that simulates what xrange does.ie. we need an iterable/generator, and not list comprehension.fxrange range above works better in this case. – ekta Aug 26 '14 at 07:14
  • 1
    Very simple solution for list comprehension, thanks! – avtomaton Jul 16 '15 at 18:34
  • 1
    @ekta Use `()` instead of `[]` to turn that into a generator. – Cees Timmerman Apr 11 '16 at 11:53
  • I only needed something simple that I expected from the native `range` -- I'm happy with this solution – juanmirocks Jan 06 '17 at 10:13
8

The problem with floating point is that you may not get the same number of items as you expected, due to inaccuracy. This can be a real problem if you are playing with polynomials where the exact number of items is quite important.

What you really want is an arithmetic progression; the following code will work quite happily for int, float and complex ... and strings, and lists ...

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

Note that this code stands a better chance of your last value being within a bull's roar of the expected value than any alternative which maintains a running total.

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

Correction: here's a competetive running-total gadget:

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

>>> list(kahan_range(0, 1, 0.0001))[-1]
0.9999
>>> list(kahan_range(0, 3333.3334, 1/3.))[-1]
3333.333333333333
>>>
John Machin
  • 81,303
  • 11
  • 141
  • 189
  • This is a wonderful answer. Why did it not get more upvotes than the answers that accumulate imprecision or need additional libraries? – josch Aug 19 '16 at 08:58
5

When you add floating point numbers together, there's often a little bit of error. Would a range(0.0, 2.2, 1.1) return [0.0, 1.1] or [0.0, 1.1, 2.199999999]? There's no way to be certain without rigorous analysis.

The code you posted is an OK work-around if you really need this. Just be aware of the possible shortcomings.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    Yes, there is a way to be certain. Your step size of 1.1 is exactly 0x1.199999999999ap+0 when represented as a double floating point number in your memory. The code that OP posted is not a perfect workaround because repeated addition of imprecise values lets the error accumulate. It's a better idea to keep track of the loop number during iteration and multiply the step size times the loop number as other answers already have shown. – josch Aug 19 '16 at 08:53
  • @josch I meant in the general case, not those numbers specifically. – Mark Ransom Aug 19 '16 at 13:18
  • Then our definitions of "certain" differ. Even with floating point numbers, a computer will not give you an "uncertain" or "random" result. The result will always be deterministic. No matter the input, given any finite floating point numbers you will always be able to say with certainty what their result is after adding them X time with a given precision. – josch Aug 19 '16 at 14:24
  • @josch I've made a couple of edits based on your feedback, thanks. – Mark Ransom Aug 19 '16 at 15:14
  • That makes sense now. Thanks! – josch Aug 20 '16 at 05:43
3

Here is a special case that might be good enough:

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

In your case this would be:

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

and so:

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

This is what I would use:

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

rather than:

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

hope it helps.

Bhushan Kawadkar
  • 28,279
  • 5
  • 35
  • 57
Andrey
  • 23
  • 6
  • This does not provide an answer to the question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient [reputation](http://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](http://stackoverflow.com/help/privileges/comment). – Rais Alam Jun 17 '14 at 04:24
  • @RaisAlam Please notice that if he used 0.5 instead of 0.1, the unexpected behavior would not appear. – Cees Timmerman Apr 11 '16 at 12:00
-2

Probably because you can't have part of an iterable. Also, floats are imprecise.

Tim McNamara
  • 18,019
  • 4
  • 52
  • 83
  • Floats are very precise. What they are not good at is representing certain values that have a finite representation in base 10. For example 0.1 in base 10 will become 0x1.999999999999ap-4 (using the exact hex float representation) in your memory which is not exactly 0.1 in decimal. With that argument you can also say that decimal numbers are imprecise because there are certain values that they have no finite representation for... – josch Aug 19 '16 at 08:49