I need a clean approach for range-checking floats in Python
Question:
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?
Answers:
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.
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). 🙂
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
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
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?
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.
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). 🙂
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
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