On Tue, Aug 2, 2016 at 2:56 PM, David Mertz <mertz@gnosis.cx> wrote:
It really doesn't make sense to me that a clamp() function would *limit to* a NaN.  I realize one can write various implementations that act differently here, but the principle of least surprise seems violated by letting a NaN be an actual end point IMO.

NaN's rarely follow the principle of least surprise :-)

In [7]: float('nan') == float('nan')
Out[7]: False

and you are not letting it be an end point -- you are returning Not a Number -- i.e. I have no idea what this value should be.

If I'm asking for a value that is "not more than (less than) my bounds"

If your bounds are NaN, then you cannot know if you value is within those bounds -- that's how NaN works.

A NaN, conceptually, is a value that *might* exist, if only we knew more and could determine it.... but as is, it's just "unknown."

NaN is often used for missing values and the like, but that's now quite what it means -- it means just what it says, NOT a number. You know nothing about it.

If someone is passing a NaN in for a bound, then they are passing in garbage, essentially -- "I have no idea what my bounds are" so garbage is what they should get back -- "I have no idea what your clamped values are".

The reality is that NaNs tend to propagate through calculations -- once one gets introduced, you are very, very likely to get NaN as a result -- this won't change that.

If you want unbounded, then don't use this function :-) -- or pass in inf or -inf -- that's what they are for. And they work for integers, too:

float('inf') > 9999999999999999999999999999999
Out[13]: True

If they don't work for other numeric types, then that should be fixed in those types...

One final thought:

How would a NaN find it's way into this function? two ways:

 1) the user specified it, thinking it might mean "unlimited" -- well don't do that! It will fail the first test.

 2) the limit was calculated in some way that resulted in a NaN -- well, in this case, they really have no idea what that limit should be -- the NaN should absolutely be propagated, like it is for any other arithmetic operation.


PS: numpy may be a good place to look for precedent, but unfortunately, it is not necessarily a good place to look for carefully thought out implementations -- much of it was put in there when someone needed it, without much discussion at all. I'm sure that NaN's behave the way they do in numpy.clip() because of how it happens to be implemented, not because anyone carefully thought it out.


Christopher Barker, Ph.D.

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception