Python: max/min builtin functions depend on parameter order
Asked Answered
D

3

58

max(float('nan'), 1) evaluates to nan

max(1, float('nan')) evaluates to 1

Is it the intended behavior?


Thanks for the answers.

max raises an exception when the iterable is empty. Why wouldn't Python's max raise an exception when nan is present? Or at least do something useful, like return nan or ignore nan. The current behavior is very unsafe and seems completely unreasonable.

I found an even more surprising consequence of this behavior, so I just posted a related question.

Dignitary answered 21/11, 2010 at 12:55 Comment(9)
Really... It doesn't seem to break on 'nan', because max(0.5, float('nan'), 1) returns 1.Platino
@khachik: I'm just saying max result depends on the order of parameters, which is a bit unexpected to me, even if it only happened in one example. But in fact it works on your example too: max(float('nan'), 1, 0.5) returns nan.Dignitary
The bug isn't in max. The bug is the fact that you're using floating points and assuming they have any meaningful mathematical behavior.Awash
@Antimony: I don't assume that floating point values perfectly represent mathematical objects. I do assume that they are useful in building software, which requires (among other things) that their behavior is consistent with the expectations of the majority of experienced developers. Anything that violates this assumption is either a bad design, a bug.Dignitary
Well floating points do not satisfy the expectations of most developers. I'll leave the question of whether they are useful in building software up to you.Awash
@Awash floating point numbers do have meaningful mathematical behavior. That is the essence of standards such as the IEEE 754.Unbutton
@ CS Floating point equality isn't even reflexive (NaN != NaN). Also, addition and multiplication are not associative. They don't fit the usual ring concept. Their behavior is highly tied to implementation, rather than any sort of abstract mathematical notion. Even worse, the behavior is sometimes platform or setting dependent.Awash
Note that there is similar behavior when you compare equal but different objects. For example, the max of 1 and 1.0 depends on the order.Subaudition
@Subaudition that seems very reasonable though. for example, let's assume that two string literals abc and abc aren't actually the same object (note that in some implementations they actually might be the same object, let's ignore this). of course, max('abc', 'abc') will return one of these literals, but which one is arbitrary and depends on the order. Precisely as you'd expect, I think?Dignitary
R
50
In [19]: 1>float('nan')
Out[19]: False

In [20]: float('nan')>1
Out[20]: False

The float nan is neither bigger nor smaller than the integer 1. max starts by choosing the first element, and only replaces it when it finds an element which is strictly larger.

In [31]: max(1,float('nan'))
Out[31]: 1

Since nan is not larger than 1, 1 is returned.

In [32]: max(float('nan'),1)
Out[32]: nan

Since 1 is not larger than nan, nan is returned.


PS. Note that np.max treats float('nan') differently:

In [36]: import numpy as np
In [91]: np.max([1,float('nan')])
Out[91]: nan

In [92]: np.max([float('nan'),1])
Out[92]: nan

but if you wish to ignore np.nans, you can use np.nanmax:

In [93]: np.nanmax([1,float('nan')])
Out[93]: 1.0

In [94]: np.nanmax([float('nan'),1])
Out[94]: 1.0
Rollandrollaway answered 21/11, 2010 at 13:10 Comment(1)
@javadba one of the IEEE-754 rules on NaN is "The comparisons EQ, GT, GE, LT, and LE, when either or both operands is NaN returns FALSE". So it's literally "all programming languages that implement floats" not "Only python ..".Sorkin
M
9

I haven't seen this before, but it makes sense. Notice that nan is a very weird object:

>>> x = float('nan')
>>> x == x
False
>>> x > 1
False
>>> x < 1
False

I would say that the behaviour of max is undefined in this case -- what answer would you expect? The only sensible behaviour is to assume that the operations are antisymmetric.


Notice that you can reproduce this behaviour by making a broken class:

>>> class Broken(object):
...     __le__ = __ge__ = __eq__ = __lt__ = __gt__ = __ne__ =
...     lambda self, other: False
...
>>> x = Broken()
>>> x == x
False
>>> x < 1
False
>>> x > 1
False
>>> max(x, 1)
<__main__.Broken object at 0x024B5B50>
>>> max(1, x)
1
Morceau answered 21/11, 2010 at 13:8 Comment(1)
For NaN comparison you should use math.isnan functionCervantez
T
1

Max works the following way:

The first item is set as maxval and then the next is compared to this value. The comparation will always return False:

>>> float('nan') < 1
False
>>> float('nan') > 1
False

So if the first value is nan, then (since the comparation returns false) it will not be replaced upon the next step.

OTOH if 1 is the first, the same happens: but in this case, since 1 was set, it will be the maximum.

You can verify this in the python code, just look up the function min_max in Python/bltinmodule.c

Telescopic answered 21/11, 2010 at 13:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.