-2

I'm using the following function in Python 2.7.3 and Kivy 1.8.0 to fade-in a Grid widget:

def __init__(self, **kwargs):
     # ...Init parent class here...
     self.grid.opacity = 0.0
     Clock.schedule_interval(self.show, 1 / 10)

def show(self, value):
    if self.grid.opacity == 1.0:
        return False
    else:
        self.grid.opacity += 0.1

show() is executed infinitely, self.grid.opacity == 1.0 always returs False, so the scheduler never removes the function.

I thought, and the documentation says, that opacity is a NumericProperty which defaults to 1.0, but I'm changing its value right before show() is called.

This is what I've tried:

if self.grid.opacity == NumericProperty(1.0):

if float(self.grid.opacity) == 1.0:

It doesn't work. Also, I'm sure self.grid.opacity is 1.0 and type() retrieves float right before I make the comparison.

This works:

if str(self.grid.opacity) == "1.0":

But obviously I don't like this solution.

Any ideas?

cdonts
  • 9,304
  • 4
  • 46
  • 72
  • 2
    http://floating-point-gui.de/errors/comparison/ – Colonel Thirty Two Aug 05 '14 at 20:45
  • 2
    There are so many resources explaining floating point numbers and their inexactness. Did you search SO (or even use google)? – PaulMcKenzie Aug 05 '14 at 20:45
  • Yes I do, but testing in the Python command line `1.0 == 1.0` gives me `True`. – cdonts Aug 05 '14 at 20:46
  • 1
    That doesn't mean `0.1 + 0.1 + 0.1 ... == 1.0` – Mark Whitfield Aug 05 '14 at 20:47
  • @cdonts - It doesn't matter what Python says. Comparison for equality of floating point numbers, even if you believe they're the same value, is not exact. – PaulMcKenzie Aug 05 '14 at 20:48
  • But right before comparing I can see `opacity` is `1.0`. – cdonts Aug 05 '14 at 20:48
  • @cdonts: Add `0.1` to itself 10 times in a python interpreter and see what you get. It isn't `1.0`. – Mark Whitfield Aug 05 '14 at 20:49
  • 1
    I think this is marked as the wrong duplicate. http://stackoverflow.com/questions/5595425/what-is-the-best-way-to-compare-floats-for-almost-equality-in-python is much more relevant here. Also no one has mentioned epsilon in Python for either question. – Selali Adobor Aug 05 '14 at 20:49
  • @cdonts Seeing 1.0 when printed doesn't mean that it's exactly 1.0. Print rounds integers off a little so we don't always have to see a ton of digits. – Roger Fan Aug 05 '14 at 20:50
  • 3
    @cdonts: you're probably getting confused because you're looking at `str(somenumber)` instead of `repr(somenumber)`. Try `print(repr(self.grid.opacity), self.grid.opacity-1)` to convince yourself you're looking at 0.9999999999999999 instead. – DSM Aug 05 '14 at 20:50
  • @AssortedTrailmix: Thanks, that is a better dupe. I've reopened but I can't close again, please do. – Greg Hewgill Aug 05 '14 at 20:51
  • @cdonts - I bet the *real* value (no pun intended) is *not* 1.0 but 1.0 plus or minus some very small number (i.e `1.000000002`). Why not do this: `float diff = opacity - 1.0;` Don't be surprised if `diff` is not exactly 0. – PaulMcKenzie Aug 05 '14 at 20:51
  • You could try `abs(self.grid.opacity- 1.0) <= sys.float_info.epsilon`. You still run into possible issues with rounding of very large numbers though. – Selali Adobor Aug 05 '14 at 20:52
  • `(1.0 == 1.0) always false` that is very doubtful. – njzk2 Aug 05 '14 at 20:55
  • 1
    Also, unless you are using `from __future__ import division`, the `1/10` argument passed to `schedule_interval` is 0, not 0.1. – chepner Aug 05 '14 at 20:58
  • 1
    @PaulMcKenzie And how exactly should they search for this particular problem? Unless you are aware of the problem (in which case you won't be asking the question in the first place), it seems to me that it would be rather difficult to search for it. The downvotes here are pretty harsh. – NullUserException Aug 05 '14 at 21:03
  • @AssortedTrailmix: something that _looks_ like 1.0 can be different from something else that also _looks_ like 1.0, but written like that, 1.0 == 1.0 is true. – njzk2 Aug 05 '14 at 21:04
  • @NullUserException - I certainly wasn't the one that downvoted. As to how to search (maybe I am more curious), I would have subtracted the two values just to see if I was going crazy. Then would have probably seen that the two values weren't the same. Then take it from there... – PaulMcKenzie Aug 05 '14 at 21:05
  • @njzk2 Naturally a number _internally_ represented as 1.0 is equal to another one _internally_ represented as 1.0, but reading the question makes it very clear that's not what is being talked about. You can nitpick about the semantics of the title, but I'd say that it does a fine job of conveying his problem and reading the question makes it very clear what it means. – Selali Adobor Aug 05 '14 at 21:08

5 Answers5

4

Might be a float comparison issue. I don't know the application, but float's are never exact, so testing them for equality can cause problems. You can try something like:

if abs(float(self.grid.opacity) - 1.0) < .001:
    pass

An example of funny float behavior, at least on my setup:

>>> .1 + .1 + .1 == .3
False
>>> .1 + .1 == .2
True
Roger Fan
  • 4,945
  • 31
  • 38
4

It is probably not Python specific. Read What Every Programmer Should Know About Floating-Point Arithmetic.

0.1 is not exactly representable as a IEEE754 double-precision floating point. So I guess that the floating point (parsed from) 0.1 (which is not exactly one tenth) is not converted as a string "0.1"

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
3

This is your problem:

>>> q=0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1+0.1
>>> str(q)
'1.0'
>>> q
0.9999999999999999
>>> q==1
False

Bottom line never compare floats with ==, period.

carlosdc
  • 12,022
  • 4
  • 45
  • 62
2

As others have stated, the problem is due to the way floating point numbers are stored. While you could try to use workarounds, there's a better way to do this: Animation.

In __init__:

self.grid.opacity = 0
anim = Animation(opacity=1)
anim.start(self.grid)
kitti
  • 14,663
  • 31
  • 49
1

@Basile Starynkevitch answers why this is happening, the nature of floating point numbers is at work here. The general form for doing this kind of comparison is:

abs(numberA - numberB) <= SOMEEPSILON

where SOMEEPSILON is a number you deem to be an acceptable margin.

If you're working with smaller numbers and not worried about a rounding error you can sys.float_info.epsilon

So as I commented by combining the two you get:

abs(self.grid.opacity- 1.0) <= sys.float_info.epsilon

The definition of epsilon's value in the docs is:

difference between 1 and the least value greater than 1 that is representable as a float

Which is a another way of saying, the smallest value between 1 and the number right before it.

So for example, if python could only represent numbers up to 2 decimal places, epsilon would be the difference between 1.00 and 0.99 (in reality the value is much smaller than that)

Selali Adobor
  • 2,060
  • 18
  • 30