A small BUG in max/min functions (PHP)-php Tutorial

Source: Internet
Author: User
A small BUG in maxmin function (PHP) is shown in the following figure:

# 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 demonstration, the ceil function returns-0.0. max also returns-0.0 when the result of the ceil function call is passed as a parameter.

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

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

The following describes the causes of these results.

Ceil returns-0.0

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

The implementation of the ceil function in $ PHP-SRC/ext/stardands/math. c ($ PHP-SRC refers to the PHP interpreter source code 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 this we can see that the ceil function has done two things:

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

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

  • Therefore, the reason why ceil returns-0.0 is that C. Write a function to test it:

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

    The execution result of the above code on my machine is-0.000000. This is a question about the C language. For more information, see http://www.wikiwand.com/zh/-0.

    -0.0 cannot be passed in directly

    Next we will discuss why the same result is not obtained when running max (-0.0, 0.

    You can use the vld extension to view the php file with only the above line of code. The result is as follows:

    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 the SEND_VAL operation for 2 is required, and the value sent in is 0. That is to say, after the lexical analysis stage,-0.0 is converted to 0. How to convert it? The following is a simple analysis process.

    The PHP lexical analyzer is generated by re2c, and the syntax analyzer is generated by Bison. In zend_language_scanner.l (under the $ 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 will be marked as DNUM in the lexical scan and is located in the zend_strtod.c zend_strtodd function, addition and subtraction are also processed, however, The-symbol does not match the DNUM (so why should zend_strtodd process addition and subtraction? Because this function is not only used here ). Here, a T_DNUMBER mark is returned.

    See zend_language_parser.y again:

    common_scalar:        T_LNUMBER                     { $$ = $1; }    |    T_DNUMBER                     { $$ = $1; }    ...;static_scalar: /* compile-time evaluated scalars */        common_scalar        { $$ = $1; }    ...    |    '+' static_scalar { ZVAL_LONG(&$1.u.constant, 0); add_function(&$2.u.constant, &$1.u.constant, &$2.u.constant TSRMLS_CC); $$ = $2; }    |    '-' static_scalar { ZVAL_LONG(&$1.u.constant, 0); sub_function(&$2.u.constant, &$1.u.constant, &$2.u.constant TSRMLS_CC); $$ = $2; }

    We also removed some details and briefly described the above syntax analysis process:

  • T_DNUMBER is a common_scalar statement;

  • Common_scalar is a static_scalar statement;

  • If a minus sign exists before the static_scalar statement, set operand 1 (op1) to ZVAL_LONG with a value of 0, and then call the sub_function function to process the two operands.

  • The sub_function function is implemented in zend_operators.c. The operation is simple, that is, the op1 value minus the op2 value, so there will be no transfer-0.0.

    Directly call or assign values to variables

    In this case, why can I directly use function calls for parameters or assign values to variables? Let's take a look at the analysis statements for function parameters in zend_language_parser.y:

    function_call_parameter_list:        '(' ')'    { Z_LVAL($$.u.constant) = 0; }    |    '(' non_empty_function_call_parameter_list ')'    { $$ = $2; }    |    '(' 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); }...;

    Three rows are hidden behind the non_empty_function_call_parameter_list statement block. The processing logic of the next three rows is actually recursive calling, which does not affect our analysis.

    Through function_call_parameter_list, we can see that there are three basic parameters of the function:

  • No parameters

  • Parameter list available

  • Yield expression

  • Here we only need to pay attention to the situations where there is a parameter list. each parameter in the parameter list is divided into three situations:

  • Expressions that do not contain variables

  • Variable

  • Reference variable

  • The first case is the direct transfer-0.0 mentioned above, and the $ a after the input value corresponds to the second case. The parameters will be finally handed over to the zend_do_pass_param function (zend_compile.c) for processing.

    What if ceil (-0.5) is passed as the parameter? In fact, this problem also corresponds to the second case. it is complicated to analyze it separately. to save some trouble, we can use vld to look at the max (ceil (-0.5), 0) execution 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  $0      'ceil'         5        EXT_FCALL_END         6        SEND_VAR_NO_REF                    6          $0         7        SEND_VAL                                      0         8        DO_FCALL                           2          'max'         9        EXT_FCALL_END   6    10      > RETURN                                        1

    In a statement with the serial number 4, the execution result of ceil is assigned to a variable of $0. in execution with the serial number 6, the SEND_VAR_NO_REF statement is executed, $0 of the call. When is the Opcode of SEND_VAR_NO_REF specified? It is 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;    ...}

    The zend_parse_parameters function (zend_API.c) is used to obtain parameters during function execution. There are still a lot of processing procedures from the storage of parameters to the acquisition. But you need to know one thing: when using variables as parameters, a function reads them directly from the list of stored variables without filtering, therefore, variables $ a or ceil (-0.5) can be passed to the max function directly.

    Last reason

    Now that we all know the above, there is still one question: why does the max function select the former in-0.0 and 0?

    In fact, this problem is very simple, take a look at the implementation of the max function ($ PHP-SRC/ext/standard/array. c) will know that when the two values are equal, select the former:

    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, min functions also have this problem. The difference is that min functions call is_smaller_function to compare two values and return the former when the two values are equal.

    To solve this problem, you 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
    Remarks

    This article is just a glimpse of the leopard. from a small "bug" entry, we will simply look at the processing process of each link. if you want to have a deeper understanding of the PHP execution process, it also requires a lot of energy and knowledge reserves.

    The execution process of PHP source code analysis not only aims to have a deeper understanding of PHP, but also helps us understand all the steps and implementations of a language from code to execution results.

    There is not much about lexical analyzer and syntax analyzer here. I hope you will have the opportunity to further explore it later. Re2c rules are relatively simple. There are many related books about Bison.

    There is a rough explanation in this article, and there are still problems. if you have any mistakes, please correct them.

    Stay foolish, stay humble; Keep questioning, keep learning.

    Private Address: http://0x1.im

    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.