Why is there no floating point implementation in the standard library?
As is clear from all the posts here, there is no floating point version of range() . However, the omission makes sense when you consider that the range() function is often used as an index generator (and, of course, this means an access method). So, when we call range(0,40) , we actually say that we want 40 values, starting from 0 to 40, but not including 40 themselves.
Given that index generation depends on both the number of indexes and their values, using the range() floating-point implementation in the standard library makes less sense. For example, if we called the frange(0, 10, 0.25) function, we would expect that 0 and 10 would be included, but this would lead to a vector with 41 values.
Thus, depending on its use, frange() will always exhibit conflicting intuitive behavior; it either has too many values perceived from the point of view of indexing, or does not contain a number that should reasonably be returned from a mathematical point of view.
Mathematical use case
With that said, as already mentioned, numpy.linspace() performs the generation from a mathematical point of view:
numpy.linspace(0, 10, 41) array([ 0. , 0.25, 0.5 , 0.75, 1. , 1.25, 1.5 , 1.75, 2. , 2.25, 2.5 , 2.75, 3. , 3.25, 3.5 , 3.75, 4. , 4.25, 4.5 , 4.75, 5. , 5.25, 5.5 , 5.75, 6. , 6.25, 6.5 , 6.75, 7. , 7.25, 7.5 , 7.75, 8. , 8.25, 8.5 , 8.75, 9. , 9.25, 9.5 , 9.75, 10. ])
Indexing Use Case
And for the prospect of indexing, I wrote a slightly different approach with some tricky string magic that allows us to specify the number of decimal places.
# Float range function - string formatting method def frange_S (start, stop, skip = 1.0, decimals = 2): for i in range(int(start / skip), int(stop / skip)): yield float(("%0." + str(decimals) + "f") % (i * skip))
Similarly, we can also use the built-in round function and specify the number of decimal places:
# Float range function - rounding method def frange_R (start, stop, skip = 1.0, decimals = 2): for i in range(int(start / skip), int(stop / skip)): yield round(i * skip, ndigits = decimals)
Quick comparison and performance
Of course, given the discussion above, these functions have a rather limited use case. However, here is a quick comparison:
def compare_methods (start, stop, skip): string_test = frange_S(start, stop, skip) round_test = frange_R(start, stop, skip) for s, r in zip(string_test, round_test): print(s, r) compare_methods(-2, 10, 1/3)
The results are identical for each:
-2.0 -2.0 -1.67 -1.67 -1.33 -1.33 -1.0 -1.0 -0.67 -0.67 -0.33 -0.33 0.0 0.0 ... 8.0 8.0 8.33 8.33 8.67 8.67 9.0 9.0 9.33 9.33 9.67 9.67
And a little time:
>>> import timeit >>> setup = """ ... def frange_s (start, stop, skip = 1.0, decimals = 2): ... for i in range(int(start / skip), int(stop / skip)): ... yield float(("%0." + str(decimals) + "f") % (i * skip)) ... def frange_r (start, stop, skip = 1.0, decimals = 2): ... for i in range(int(start / skip), int(stop / skip)): ... yield round(i * skip, ndigits = decimals) ... start, stop, skip = -1, 8, 1/3 ... """ >>> min(timeit.Timer('string_test = frange_s(start, stop, skip); [x for x in string_test]', setup=setup).repeat(30, 1000)) 0.024284090992296115 >>> min(timeit.Timer('round_test = frange_r(start, stop, skip); [x for x in round_test]', setup=setup).repeat(30, 1000)) 0.025324633985292166
It looks like the string formatting method wins on my system.
Limitations
And finally, a demonstration of the essence of the discussion above and another limitation:
# "Missing" the last value (10.0) for x in frange_R(0, 10, 0.25): print(x) 0.25 0.5 0.75 1.0 ... 9.0 9.25 9.5 9.75
In addition, when the skip parameter is not divided by the stop value, a gaping gap may occur, taking into account the last problem:
# Clearly we know that 10 - 9.43 is equal to 0.57 for x in frange_R(0, 10, 3/7): print(x) 0.0 0.43 0.86 1.29 ... 8.14 8.57 9.0 9.43
There are ways to solve this problem, but in the end, the best approach is probably to just use Numpy.