1

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
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
vibhud
  • 95
  • 1
  • 5
  • 2
    Alternatively, you can use `numpy.linspace`: https://numpy.org/doc/stable/reference/generated/numpy.linspace.html . Floating point numbers in general will have the issue you're observing – jrd1 Oct 05 '20 at 22:49
  • 1
    You can replace `stop + step` with `stop + epsilon`, [here](https://stackoverflow.com/questions/19141432/python-numpy-machine-epsilon) you can see how to get an `epsilon` – fas Oct 05 '20 at 22:51
  • There's no such thing as 0.1. Not marking as a duplicate, but see https://stackoverflow.com/q/588004/2988730 – Mad Physicist Oct 06 '20 at 02:59
  • 1
    @MadPhysicist Thanks for your answer, I'm aware of floating point characteristics, mainly I'm looking for other alternatives. I end up using linspace for now. – vibhud Oct 06 '20 at 16:33

2 Answers2

1

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.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1

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

Amadan
  • 191,408
  • 23
  • 240
  • 301
  • I like the idea but in this specific case I want an user to input start, stop and step information. I think I will have to do more transformation to use the form. – vibhud Oct 06 '20 at 16:28