tags:

views:

122

answers:

4

I'm looking for a simple way of range checking floats in Python where the minimum and maximum bounds may be null.

The code in question is:

    tval = float(-b - discriminant) / float (2*a)
    if tval >= tmin and tval <= tmax:
        return tval 

    tval = float(-b + discriminant) / float (2*a)
    if tval >= tmin and tval <= tmax:
        return tval

    # Neither solution was within the acceptable range.
    return None

However, this completely fails to handle the case where tmin or tmax is None (which should be interpreted to mean that there is no minimum or maximum respectively).

So far the best I've been able to come up with is:

    tval = float(-b - discriminant) / float (2*a)
    if (tmin == None or tval >= tmin) and (tmax == None or tval <= tmax):
        return tval 

    tval = float(-b + discriminant) / float (2*a)
    if (tmin == None or tval >= tmin) and (tmax == None or tval <= tmax):
        return tval

    # Neither solution was within the acceptable range.
    return None

I keep thinking there has to be a better (cleaner, more readable) way to write that. Any ideas?

+3  A: 

First, some setup: we'll need a float infinity constant.

INF = float(1e3000)

or

INF = float('inf')  # Python 2.6+

First option can be considered portable for practical purposes; just use some really huge value which is guaranteed to be outside the range representable by your platform's floating point type. Second option is "truly" portable, but requires Python 2.6 or newer.

Now, your condition can be written this way (edit: only if tmin and tmax cannot be zero!):

if (tmin or -INF) <= tval <= (tmax or +INF) :
    return tval

Edit: I made a crude error by missing that 0.0 is a legitimate value for tmin or tmax. Thanks to Roger Pate for noticing. Guys, please retract your upvotes, this answer doesn't work...

atzz
@atzz, I suspect Adam would add this back in, but in any case it would be clearer code if you explicitly included his "return None" (perhaps with the comment and an "else" in front), so no maintainer ever thinks it's a bug when the if condition fails and you get the implicit return of None.
Peter Hansen
Fails for 0.0 values of tmin and tmax.
Roger Pate
@Peter Hansen -- uh, what "else"? what implicit return? I just took the `if` statement from the question as-is. The change described here affects condition only. Other changes are out of scope of the question.
atzz
@Roger Pate -- you're right!
atzz
I'll leave my upvote. Even if it's not a complete answer, it inspired some of the other solutions and managed to show me a unit-test I didn't know I needed.
Adam Luchjenbroers
atzz: Actually, I downvoted when you had the incorrect and misleading answer, and now that it's fixed, I'll retract that and upvote. I still think there's an even better (in terms of clarity) solution (the one I prefer in my answer) though. :)
Roger Pate
+4  A: 

Maybe I'd define a checker function first, for readability:

def inrange(x, min, max):
    return (min is None or min <= x) and (max is None or max >= x)

tval = float(-b - discriminant) / float (2*a)
if inrange(tval, tmin, tmax):
    return tval 

tval = float(-b + discriminant) / float (2*a)
if inrange(tval, tmin, tmax):
    return tval 

# Neither solution was within the acceptable range.
return None

There must be a module defining such an inrange method somewhere, I bet. But I did not find it (nor look for it, though). :)

jellybean
+3  A: 

Using INF from atzz's answer (in a way that won't fail when 0.0 is used):

def coalesce(*values):
  for v in values:
    if v is not None:
      return v

if coalesce(tmin, -INF) <= tval <= coalesce(tmax, INF):
  return tval

However, what you have is clear enough for me:

if ((tmin is None or tmin <= tval) and
    (tmax is None or tval <= tmax)):
   return tval
Roger Pate
+2  A: 

Using INF from atzz's answer

if tmin is None: tmin = -INF
if tmax is None: tmax = +INF

tval = float(-b - discriminant) / float (2*a)
if tmin <= tval <= tmax:
    return tval 

tval = float(-b + discriminant) / float (2*a)
if tmin <= tval <= tmax:
    return tval

# Neither solution was within the acceptable range.
return None
gnibbler
This works beautifully. As tmin and tmax are actually received as keyword arguments I can simply make the default values for them -/+INFINITY instead of None. This actually works out to be a more sensible default.
Adam Luchjenbroers