A small BUG in the Max/min function (PHP)

Source: Internet
Author: User
Take a direct look at the show:

# Psy Shell v0.3.3 (PHP 5.5.30-cli) by Justin hileman>>> ceil ( -0.5) = -0.0>>> max ( -0.0, 0) = 0.0 >>> Max (Ceil ( -0.5), 0) = 0.0

In the above demo, the Ceil function returns a 0.0 when -0.0,max passes the result of the Ceil function call as a parameter.

If you assign a value to a variable for the ceil result, you can still get the result of-0.0:

>>> $a = ceil ( -0.5) = -0.0>>> max ($a, 0) +-0.0

Here are some of the reasons why these results are produced.

Ceil will return-0.0

First let's take a look at why the Ceil function returns-0.0.

Ceil function implementation in $PHP-SRC/EXT/STARDANDS/MATH.C ($PHP-src refers to the PHP interpreter source root directory), in order to show clearly I removed some details:

Php_function (ceil) {    ...    if (z_type_pp (value) = = is_double) {        return_double (ceil (z_dval_pp (value)));    } else if (z_type_pp (value) = = Is_ LONG) {        convert_to_double_ex (value);        Return_double (z_dval_pp (value));    }    ...}

From here you can see that the Ceil function does two things:

  • If the argument type is double, the ceil function of the C language is called directly and the execution result is returned;

  • If the argument type is long, it is converted to double and returned directly.

  • So Ceil returns-0.0 The reason for this is also C. Write a function test:

       int main (int argc, char const *argv[]) {    printf ("%f\n", Ceil (- 0.5));    return 0;}

    The result of the above code on my machine is-0.000000. As to why this is the result, this is the C language problem, here is not elaborate, interested can see here: http://www.wikiwand.com/zh/-0.

    Cannot pass in directly-0.0

    The next step is to discuss why Max (-0.0, 0) does not get the same result.

    Use the VLD extension to see the results of a PHP file with only one line of code:

    Line     #* E I O op                    fetch      ext  return  Operands--------------------------------------------------------------------------   3     0  E >   ext_stmt         1        ext_fcall_begin         2        send_val                                      0         3        send_val                                      0         4        do_ Fcall                           2          ' Max '         5        ext_fcall_end   5     6      > RETURN                                        1

    Note that a send_val operation of 2 is required, and the value sent in is 0. It is said that after the lexical analysis phase-0.0 is converted to 0. How is it converted? Here's a simple analysis of the process.

    The parser for PHP is generated by RE2C, and the parser is generated by Bison. In the ZEND_LANGUAGE_SCANNER.L ($PHP-src/zend directory) We can find the following statement:

    Lnum    [0-9]+dnum    ([0-9]*]. " [0-9]+) | ([0-9]+]. " [0-9]*) Exponent_dnum    ({lnum}|{ Dnum}) [ee][+-]? {Lnum}) ......
      {dnum}|{ Exponent_dnum} {    zendlval->value.dval = Zend_strtod (Yytext, NULL);    Zendlval->type = is_double;    return t_dnumber;}

    Lnum and Dnum are followed by simple regular expressions. Although 0.0 is marked as dnum in the lexical scan, and in the ZEND_STRTOD.C zend_strtod function, there are also handles to the minus sign, but the-symbol does not match the Dnum (then why do Zend_strtod have to deal with the minus sign?). Because this function is not just used here). This will eventually return a t_dnumber tag.

    Then look at the ZEND_LANGUAGE_PARSER.Y:

    Common_scalar:        t_lnumber                     {$$ = $;}    |    T_dnumber                     {$$ = $;}    ...; Static_scalar:/* compile-time evaluated scalars */        common_scalar        {$$ = $;}    ...    |    ' + ' Static_scalar {zval_long (&$1.u.constant, 0); Add_function (&$2.u.constant, &$1.u.constant, &$2. U.constant tsrmls_cc); $$ = $; }    |    ' -' Static_scalar {zval_long (&$1.u.constant, 0); Sub_function (&$2.u.constant, &$1.u.constant, &$2. U.constant tsrmls_cc); $$ = $; }

    We also removed some details and briefly described the process of parsing above:

  • T_dnumber is a common_scalar statement;

  • Common_scalar is a static_scalar statement;

  • When a minus sign precedes the static_scalar statement, the operand 1 (OP1) is set to the Zval_long with a value of 0, and then the Sub_function function is called to process two operands.

  • The implementation of the Sub_function function is located in ZEND_OPERATORS.C, and the operation is simple, which is to subtract the value of OP2 with the value of OP1, so there is no case of an incoming-0.0.

    Call or assign a value directly to a variable

    In that case, why is it possible to use a function call to make arguments or assign variables to a variable in a way that can be passed in? Take a moment to look at the parse statement for the function parameter in ZEND_LANGUAGE_PARSER.Y:

    Function_call_parameter_list:        ' (') '    {z_lval ($$.u.constant) = 0;}    |    ' (' non_empty_function_call_parameter_list ') '    {$$ = $;}    |    ' (' yield_expr ') '    {z_lval ($$.u.constant) = 1; Zend_do_pass_param (&$2, Zend_send_val, Z_lval ($$.u.constant) TSRMLS_CC); };non_empty_function_call_parameter_list:        expr_without_variable    {z_lval ($$.u.constant) = 1;  Zend_do_pass_param (&$1, Zend_send_val, Z_lval ($$.u.constant) tsrmls_cc); }    |    Variable                {z_lval ($$.u.constant) = 1;  Zend_do_pass_param (&$1, Zend_send_var, Z_lval ($$.u.constant) tsrmls_cc); }    |    ' & ' w_variable                 {z_lval ($$.u.constant) = 1;  Zend_do_pass_param (&$2, Zend_send_ref, Z_lval ($$.u.constant) tsrmls_cc); }...;

    In order to visualize the Non_empty_function_call_parameter_list statement block, I went to three lines behind. The processing logic for the next three rows is actually a recursive call and does not affect our analysis.

    By function_call_parameter_list you can see that the parameters of the function include three kinds of basic conditions:

  • No parameters

  • have parameter list

  • Yield expression

  • Here we only need to focus on the case with the parameter list, each parameter in the parameter list is divided into three kinds of cases:

  • Expressions that do not contain variables

  • Variable

  • Reference variable

  • In the first case, the direct pass-0.0, which we mentioned above, corresponds to the second case when the $a of the incoming assignment. The parameters will eventually be given to the Zend_do_pass_param function (zend_compile.c) for processing.

    What about passing in ceil (-0.5) as a parameter? Actually also corresponds to the second situation, this problem alone analysis is also more complicated, save a little bit we'll just take a look at VLD. Execute Max (Ceil (-0.5), 0) process:

    Line     #* E I O op                   fetch       ext  return  Operands--------------------------------------------------------------------------   5     0  E >   ext_stmt         1        ext_fcall_begin         2        ext_fcall_begin         3        send_val                                      -0.5         4        do_ Fcall                           1      ' ceil '         5        ext_fcall_end         6        send_var_no_ref 6 $         7        send_val                                      0         8        do_fcall                           2          ' Max '         9        ext_fcall_end   6      > RETURN                                        1

    In the statement with the ordinal 4, the execution result of Ceil is assigned to a variable of $ A, whereas in the execution of the ordinal 6, the SEND_VAR_NO_REF statement is executed, and the call is $ A. When was the Opcode of Send_var_no_ref appointed? Also in the Zend_do_pass_param function:

    if (op = = Zend_send_var && zend_is_function_or_method_call (param)) {/    * method call */    op = Zend_send_var _no_ref;    ...}

    Use the Zend_parse_parameters function (ZEND_API.C) during function execution to get the arguments. There is a lot of processing from the storage of parameters to the middle of the acquisition, which is no longer one by one detailed. But one thing to know: The function is read directly from the list of stored variables when using variables as parameters, and is not filtered, so the variable $a or ceil (-0.5) can pass 0.0 directly to the MAX function.

    The last Reason

    Since all this is known, there is one more question: Why do the max functions in-0.0 and 0 Choose the former?

    In fact, the problem is simple, look at the implementation of the Max function ($PHP-src/ext/standard/array.c) and know that it is true that the former is selected when the two values are equal:

    max = Args[0];for (i = 1; i < argc; i++) {    is_smaller_or_equal_function (&result, *args[i], *max tsrmls_cc); 
       if (z_lval (Result) = = 0) {        max = args[i];}    }

    Similarly, the Min function also has this problem, the difference is that the Min function is called Is_smaller_function to compare two values, two values equal to return the former.

    So to solve this problem is also very simple, only need to change the parameter order:

    # Psy Shell v0.3.3 (PHP 5.5.30-cli) by Justin hileman>>> Max (0, Ceil ( -0.5)) = 0


    This article is just glimpse, from a small "bug" entry simply comb the process of each link, if you want a deeper understanding of PHP implementation process, but also need a lot of energy and knowledge reserves.

    The analysis of the PHP code execution process is not only to have a deeper understanding of PHP, but also to help us understand a language from the code to the implementation of the results in the middle of each link and implementation.

    On the lexical analyzer and parser, there is not much to say here, I hope that there is a chance to further explore. RE2C rules are relatively simple, about Bison, there are a lot of related books.

    The text has superficial relief, but also left a problem, if there are errors, please correct me.

    Stay Foolish,stay Humble; Keep Questioning,keep Learning.

    Private Blog Address: http://0x1.im

  • Related Article

    Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.