On Sat, Jul 04, 2020 at 11:54:47AM -0700, Christopher Barker wrote:
Hmm.
Since NaN is neither greater than nor less that anything, it seems the only correct answer to any Min,max,clamp involving a NaN is NaN.
If you want NAN-poisoning behaviour it is easy for you to add it yourself as a wrapper function, without it costing any more than it would cost to have that behaviour baked into the function. You get to choose if you want to handle floats only, or Decimals, or both, what to do with signalling NANs, or whether to bother at all. If you know your bounds are not NANs, why test for them at all? But if you bake special NAN poisoning behaviour in to the function, nobody can escape it. Everyone has to test for NANs, whether they need to or not, and worse, if they want non-poisoned behaviour, they have to test for NANs *twice*, once to over-ride the builtin behaviour, and then a second time when they call the function. When we have a choice of behaviours and no absolutely clear right or wrong choice, we should choose the behaviour that inconveniences people the least. The beauty of the implementation I give is that the behaviour with NANs follows automatically, without needing to test for them. NAN poisoning requires testing for NANs, and that is not so easy to get right: py> math.isnan(2**10000) Traceback (most recent call last): File "<stdin>", line 1, in <module> OverflowError: int too large to convert to float py> math.isnan(Decimal('sNAN')) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: cannot convert signaling NaN to float You want everyone to pay the cost of testing for NANs, even if they don't want or need to. I say, let only those who need to test for NANs actually test for NANs. Why is this a debate we need to have? In practice, the most common use-case for clamp will be to call it multiple times against different values but with the same bounds: # Not likely to be this for value in values: lower = something_different_each_time() upper = something_different_each_time() do_something(clamp(value, lower, upper)) # Most commonly this: for value in values: do_something(clamp(value, lower, upper)) If you care about the remote possibility of the bounds being NANs, and want to return NAN instead, hoist the test outside of the call: # TODO: Need to catch OverflowError, maybe ValueError # maybe even TypeError? if math.isnan(lower) or math.isnan(upper): for value in values: do_something(NAN) else: for value in values: do_something(clamp(value, lower, upper)) and you only pay the cost once. Don't make everyone pay it over and over and over again when the bounds are known to not be NANs. -- Steven