Reputation: 95
For the code below, I want the range to stop at value 1.0, but it keeps going up to 1.099999..., which makes sense as it's a float value.
What would be the better way to create this range with step of 0.1?
import numpy as np
start = 0.5
stop = 1.0
step = 0.1
for parameter_value in np.arange(start, stop + step, step):
print(parameter_value)
Output
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
1.0999999999999999
Upvotes: 1
Views: 5677
Reputation: 31
It is necessary to know the number of iteration to approch the stop.
With
start = 0.5
stop = 1.0
step = 0.1
a = np.arange(start, stop , step)
for parameter_value in a:
print(parameter_value)
It's missing one value.
So it is necessary to do something like that :
a = np.arange(start, stop + step , step)
but in this case
start = -1.0
stop = 0.9799
step = 0.33
a = np.arange(start, stop + step, step)
gives
-1.0
-0.6699999999999999
-0.33999999999999986
-0.009999999999999787
0.3200000000000003
0.6500000000000004
0.9800000000000004
We know that 0.9800 > 0.9799
Even with that
a = np.arange(start, stop + step * 1e-3, step)
it gives the same bad result
-1.0
-0.6699999999999999
-0.33999999999999986
-0.009999999999999787
0.3200000000000003
0.6500000000000004
0.9800000000000004
but with
a = np.arange(start, stop + step * 1e-4, step)
0.9800000000000004 will not be included.
So, here 1e-4 gives no error with a stop whitch contains 2 digit after the comma.
With
a = np.arange(start, stop + step * 1e-9, step)
it get gives no error with a stop whitch contains 7 digit after the comma.
But in the case of
start = 0.5
stop = 1.0
step = 0.1
It gives
0.5
0.6
0.7
0.7999999999999999
0.8999999999999999
0.9999999999999999
It gets somewhat dirty results.
With linspace it is more concise, but it is necessary to know the number of iteration or the number of points in the result.
This function can help:
def give_array(start, stop, step):
n = int((stop - start) / step) # n jump
num_points = n + 1
new_stop = start + n * step
return np.linspace(start, new_stop, num_points)
where n is the number of ieration such that start + n * step <= stop
With
a = give_array(start, stop, step)
for parameter_value in a:
# print("%.4f" % (parameter_value))
print(parameter_value)
It gives cleaner result :
0.5
0.6
0.7
0.8
0.9
1.0
Likewise there is also a proposed solution here
Upvotes: 0
Reputation: 198446
As an alternative, you can use the fact that division doesn't incur as much error as iterated addition. This is as precise as floating point arithmetic can get you:
np.arange(5, 11) / 10.0
# => array([0.5, 0.6, 0.7, 0.8, 0.9, 1. ])
(np.linspace
uses a very similar formula, though as long as you are working with rational numbers, the code above may be easier to grasp in your use case - you don't have to calculate the number of steps yourself.)
Upvotes: 1
Reputation: 114478
You can not represent 0.1 exactly in binary IEEE 754 format, which is what most modern architectures use internally to represent floating point numbers.
The closest binary value to 0.1 is just a shade under truth. When you add this nearest approximation five times, the error will increase.
According to the docs for np.arange
:
When using a non-integer step, such as 0.1, the results will often not be consistent. It is better to use
numpy.linspace
for these cases.
The reason is that by clamping the ends, linapace
can make some assurances about the cumulative error which arange
can not. Each individual step used by linspace
may not be the closest binary representation of 0.1, but each element will be as close as possible to n * 0.1
from the start and end.
The linapace
call corresponding to np.arange(0.5, 1.1, 0.1)
is
np.linspace(0.5, 1.0, 6)
The other advantage of linspace
is that you fix the number of points. Also according to the arange
docs (the return value):
For floating point arguments, the length of the result is
ceil((stop - start)/step)
. Because of floating point overflow, this rule may result in the last element of out being greater than stop.
Upvotes: 1