php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #53104 min() and max() treat NULL and BOOL differently
Submitted: 2010-10-19 19:39 UTC Modified: 2013-11-10 21:44 UTC
Votes:6
Avg. Score:4.3 ± 0.7
Reproduced:5 of 5 (100.0%)
Same Version:2 (40.0%)
Same OS:0 (0.0%)
From: frase at cs dot wisc dot edu Assigned:
Status: Not a bug Package: Scripting Engine problem
PHP Version: any OS: any
Private report: No CVE-ID:
 [2010-10-19 19:39 UTC] frase at cs dot wisc dot edu
Description:
------------
The min() and max() functions treat null values as "negative infinity", which is not documented or (to me) particularly intuitive.  I would expect null to either be treated as 0 (as "(int)null" does), or ignore it entirely (which min() does not, but max() does by virtue of any value being greater than negative infinity).

Test script:
---------------
echo "min(-1,null) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,null) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,null) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,null) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */


Expected result:
----------------
min(-1,null) = int(-1)
min( 1,null) = int(1 or 0)
max(-1,null) = int(-1 or 0)
max( 1,null) = int(1)


Actual result:
--------------
min(-1,null) = NULL
min( 1,null) = NULL
max(-1,null) = int(-1)
max( 1,null) = int(1)


Patches

null_comparison_warning (last revision 2012-04-26 20:51 UTC) by roeitell at gmail dot com)

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2012-04-26 20:52 UTC] roeitell at gmail dot com
Actually changing behavior might cause serious bc issues for some users possibly 
relying on this; but attached is a patch which generates E_WARNING for min/max 
receiving a NULL parameter.
 [2013-10-23 07:46 UTC] yohgaki@php.net
-Summary: min() and max() treat null differently +Summary: min() and max() treat NULL and BOOL differently -Status: Open +Status: Analyzed -Package: Math related +Package: Scripting Engine problem -Operating System: Windows 2000 Pro SP4 +Operating System: any -PHP Version: 5.3.3 +PHP Version: any
 [2013-10-23 07:46 UTC] yohgaki@php.net
The comparison is done by compare_function() in zend_operators.c Not only NULL, but also BOOL type also has the same problem. I haven't check fully for ARRAY, but it seems NULL and BOOL break comparison.

We have inconsistency with comparison operators.

$ php -r "var_dump(-1 > NULL);"
bool(true)
$ php -r "var_dump(-1 < NULL);"
bool(false)
$ php -r "var_dump(min(-1,NULL));"
NULL
$ php -r "var_dump(min(NULL, -1));"
NULL

Comparison operators evaluate comparison as PHP users expect, but min() does not. This behavior is not intuitive.

We may fix this issue or document this unexpected behavior in min() manual. (+ other functions if there are affected)

I think this is better to be fixed even if there is BC issue at some point.
Any comments?


Test code
-----------------------------
<?php

echo "min(-1,null) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,null) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,null) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,null) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(-1,false) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,false) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,false) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,false) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(-1,true) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,true) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,true) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,true) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(null,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(null, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(null,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(null, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(false,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(false, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(false,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(false, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(true,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(true, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(true,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(true, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(10,-1,null) = "; var_dump(min(10,-1,null)); echo "\n"; /* NULL    */
echo "min(10, 1,null) = "; var_dump(min(10, 1,null)); echo "\n"; /* NULL    */
echo "max(10,-1,null) = "; var_dump(max(10,-1,null)); echo "\n"; /* int(10) */
echo "max(10, 1,null) = "; var_dump(max(10, 1,null)); echo "\n"; /* int(10)  */

echo "min(10,-1,false) = "; var_dump(min(10,-1,null)); echo "\n"; /* NULL    */
echo "min(10, 1,false) = "; var_dump(min(10, 1,null)); echo "\n"; /* NULL    */
echo "max(10,-1,false) = "; var_dump(max(10,-1,null)); echo "\n"; /* int(10) */
echo "max(10, 1,false) = "; var_dump(max(10, 1,null)); echo "\n"; /* int(10)  */

echo "min(10,-1,true) = "; var_dump(min(10,-1,null)); echo "\n"; /* NULL    */
echo "min(10, 1,true) = "; var_dump(min(10, 1,null)); echo "\n"; /* NULL    */
echo "max(10,-1,true) = "; var_dump(max(10,-1,null)); echo "\n"; /* int(10) */
echo "max(10, 1,true) = "; var_dump(max(10, 1,null)); echo "\n"; /* int(10)  */

echo "min([10,-1,null]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,null]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,null]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,null]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */

echo "min([10,-1,false]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,false]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,false]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,false]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */

echo "min([10,-1,true]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,true]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,true]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,true]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */

-------------------
 [2013-10-23 08:23 UTC] yohgaki@php.net
I forgot to edit values. So BOOL works some what but it does not compared as operators.

[yohgaki@dev php-5.5]$ php -r "var_dump(min([2, 3, false]));"
bool(false)
[yohgaki@dev php-5.5]$ php -r "var_dump(min([-2, -3, false]));"
bool(false)
[yohgaki@dev php-5.5]$ php -r "var_dump(-3 < false);"
bool(false)

[yohgaki@dev php-5.5]$ php -r "var_dump(min([2, 3, true]));"
int(2)
[yohgaki@dev php-5.5]$ php -r "var_dump(max([2, 3, true]));"
int(3)
[yohgaki@dev php-5.5]$ php -r "var_dump(max(-2, -3, true));"
int(-2)

It seems "false" is evaluated as the smallest and "true" is ignored.

We have serious mess here.
Since min()/max() supports array type, we cannot convert NULL/BOOL parameter to INT as it would returns different results for array parameter. For the same reason, black listing NULL/BOOL parameters do not work.

Anyone has clever idea?

Corrected test code
-------------
<?php

echo "min(-1,null) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,null) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,null) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,null) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(-1,false) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,false) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,false) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,false) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(-1,true) = "; var_dump(min(-1,null)); echo "\n"; /* NULL    */
echo "min( 1,true) = "; var_dump(min( 1,null)); echo "\n"; /* NULL    */
echo "max(-1,true) = "; var_dump(max(-1,null)); echo "\n"; /* int(-1) */
echo "max( 1,true) = "; var_dump(max( 1,null)); echo "\n"; /* int(1)  */

echo "min(null,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(null, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(null,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(null, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(false,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(false, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(false,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(false, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(true,-1) = "; var_dump(min(null,-1)); echo "\n"; /* NULL    */
echo "min(true, 1) = "; var_dump(min(null, 1)); echo "\n"; /* NULL    */
echo "max(true,-1) = "; var_dump(max(null,-1)); echo "\n"; /* int(-1) */
echo "max(true, 1) = "; var_dump(max(null, 1)); echo "\n"; /* int(1)  */

echo "min(10,-1,null) = "; var_dump(min(10,-1,null)); echo "\n"; /* NULL    */
echo "min(10, 1,null) = "; var_dump(min(10, 1,null)); echo "\n"; /* NULL    */
echo "max(10,-1,null) = "; var_dump(max(10,-1,null)); echo "\n"; /* int(10) */
echo "max(10, 1,null) = "; var_dump(max(10, 1,null)); echo "\n"; /* int(10)  */

echo "min(10,-1,false) = "; var_dump(min(10,-1,false)); echo "\n"; /* false    */
echo "min(10, 1,false) = "; var_dump(min(10, 1,false)); echo "\n"; /* false    */
echo "max(10,-1,false) = "; var_dump(max(10,-1,false)); echo "\n"; /* int(10) */
echo "max(10, 1,false) = "; var_dump(max(10, 1,false)); echo "\n"; /* int(10)  */

echo "min(10,-1,true) = "; var_dump(min(10,-1,true)); echo "\n"; /* -1    */
echo "min(10, 1,true) = "; var_dump(min(10, 1,true)); echo "\n"; /* 1    */
echo "max(10,-1,true) = "; var_dump(max(10,-1,true)); echo "\n"; /* int(10) */
echo "max(10, 1,true) = "; var_dump(max(10, 1,true)); echo "\n"; /* int(10)  */

echo "min([10,-1,null]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,null]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,null]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,null]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */

echo "min([10,-1,false]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,false]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,false]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,false]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */

echo "min([10,-1,true]) = "; var_dump([min(10,-1,null)]); echo "\n"; /* [NULL]    */
echo "min([10, 1,true]) = "; var_dump([min(10, 1,null)]); echo "\n"; /* [NULL]    */
echo "max([10,-1,true]) = "; var_dump([max(10,-1,null)]); echo "\n"; /* [int(10)] */
echo "max([10, 1,true]) = "; var_dump([max(10, 1,null)]); echo "\n"; /* [int(10)]  */
 [2013-11-10 21:44 UTC] yohgaki@php.net
-Status: Analyzed +Status: Not a bug
 [2013-11-10 21:44 UTC] yohgaki@php.net
Apparently, I also misunderstood that BOOL/NULL evaluated as INT, but it's evaluated as BOOL.

Therefore, this is not a bug.

NULL or FALSE < (-1000) 

is evaluated as (both operands are converted to bool)

FALSE < (bool)(-1000)

and 

(FALSE < TRUE) is FALSE
 
PHP Copyright © 2001-2015 The PHP Group
All rights reserved.
Last updated: Tue Mar 31 08:03:14 2015 UTC