0

I have encountered a very bizarre problem. I am trying to create a function that returns an array of values that enclose a range with a certain step size (such as you might find on the axis of a plot). Instead of just using np.arange(min,max,step), I want something that rounds the step size better. Here is what I tried:

def get_decade(value):
    return pow(10,math.floor(math.log10(value)))

def get_steparray(min,max,delta):
    delta_step = float(get_decade(delta))
    next = math.floor(min/delta_step)*delta_step
    print next
    array = [next]
    while next < max:
        next = int((next+delta_step)/delta_step)*delta_step
        print next
        array.append(next)
        print array
    print array
    return array

The print statements are in there to help me figure out what is going on. Here is what I tried running it with:

print get_steparray(1.032,1.431,0.1)

From this, I was expecting the array to end up as [1.0,1.1,1.2,1.3,1.4,1.5]

Here is what I get from the function:

1.0
1.1
[1.0, 1.1]
1.2
[1.0, 1.1, 1.2000000000000002]
1.3
[1.0, 1.1, 1.2000000000000002, 1.3]
1.4
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001]
1.5
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5]
[1.0, 1.1, 1.2000000000000002, 1.3, 1.4000000000000001, 1.5]

As you can see, some of them work, and others have the extra decimals added.

Anyone have any ideas what might be causing this? Thanks for any insight you can provide. Alternatively, I would be just as happy if someone knew a better, more functional way to create such an array. Maybe I should just stick to using np.arange and adjust the max/min/step values?

(Yes, I know my code isn't the cleanest. The function above started out much cleaner, but I added some unnecessary features to it to try to get it to work.)

Edit: While I appreciate everyone's insightful comments, I am still not sure that they address my issue fully. As is visible in the printout, each value is stored to sufficient precision of my needs when as an isolated float type. But when they are added to the array, it is only then that they change precision. I am quite aware of the issues with floating points, in general, but was curious about the specific differences between the float and the array. I wonder if maybe the array is storing the value in a lower number of bits than the individual value.

That being said, I think the suggestion to focus more on formatting at the point of usage is what I will end up doing.

Thanks!

TMar
  • 9
  • 3
  • Please [have a look at this.](https://docs.python.org/3.5/tutorial/floatingpoint.html) – TNW Jan 24 '16 at 15:08
  • Once you have read and digested the link provided by TNW, why not use the `round(number[, ndigits])` function to force the issue in the direction that you want. – Rolf of Saxony Jan 24 '16 at 15:16
  • I'd just do something like `[x / 10.0 for x in range(10, 16)]`. That is, stick with integers as long as possible. – Stefan Pochmann Jan 24 '16 at 16:19
  • Note that that is better than `[x * 0.1 for x in range(10, 16)]`, because 10.0 is represented exactly, unlike 0.1, which is already inexact and gets its inexactness magnified when multiplied with x. – Stefan Pochmann Jan 24 '16 at 16:22

1 Answers1

1

Why?

This is a typical Floating Point Arithmetic Issue.

I'll post here an extract of the Python documentation for floating-point arithmetic issues and limitations, but this is true for most languages:

Floating-point numbers are represented in computer hardware as base 2 (binary) fractions. For example, the decimal fraction

0.125

has value 1/10 + 2/100 + 5/1000, and in the same way the binary fraction

0.001

has value 0/2 + 0/4 + 1/8. These two fractions have identical values, the only real difference being that the first is written in base 10 fractional notation, and the second in base 2.

Unfortunately, most decimal fractions cannot be represented exactly as binary fractions. A consequence is that, in general, the decimal floating-point numbers you enter are only approximated by the binary floating-point numbers actually stored in the machine.

The problem is easier to understand at first in base 10. Consider the fraction 1/3. You can approximate that as a base 10 fraction:

0.3

or, better,

0.33

or, better,

0.333

and so on. No matter how many digits you’re willing to write down, the result will never be exactly 1/3, but will be an increasingly better approximation of 1/3.


How?

So now that you know the why, here's how you can go around this.

If you do not need all this precision (or in that matter... all this imprecision) you could always :

  • use the round builtin
  • keep the number as it is, but format it just when you need it printed for GUI or console
arainone
  • 1,928
  • 17
  • 28