简体   繁体   中英

Unexpected result in PHP stacked ternary operator

I am using PHP to try to get a percentage by dividing one number by another. However if either number is falsey, the default result should be 0.0. I'm using a ternary operation to determine what the result. However, it appears to default to the last calculation, which clearly equates to a divide by zero error. Any ideas?

The code:

$countOne = 3;
$countTwo = 0;
echo (! $countOne || ! $countTwo) ? 'true' : 'false';

$number =
    (! $countOne || ! $countTwo) ?
        0.0 :
            ($countOne > $countTwo) ?
                $countTwo / $countOne :
                    $countOne / $countTwo;

echo $number;

I'll use an if statement for now, but I can't see why the above wouldn't work.

TEST: http://sandbox.onlinephpfunctions.com/code/83f737ab27fb046a8eb9feb4992d5dd26340723d

Many years ago, the creator of PHP made a mistake which is now too late to fix, resulting in an unhelpful "associativity" of the ternary operator, which is described in a note on the manual page .

It is recommended that you avoid "stacking" ternary expressions. PHP's behaviour when using more than one ternary operator within a single statement is non-obvious:

... this is because ternary expressions are evaluated from left to right

So when you wrote this:

$number =
    (! $countOne || ! $countTwo) ?
        0.0 :
            ($countOne > $countTwo) ?
                $countTwo / $countOne :
                    $countOne / $countTwo;

You expected it to be understood by PHP as:

$number =
    (
        (! $countOne || ! $countTwo) 
            ? 0.0
            : (
                ($countOne > $countTwo)
                    ? $countTwo / $countOne
                    : $countOne / $countTwo
               )
    );

That is, perform the first test, then either give the final result 0.0 , or proceed to the second test.

But PHP actually understands it as:

$number =
    (
        (! $countOne || ! $countTwo) 
            ? 0.0
            : ($countOne > $countTwo)
    )
    ? $countTwo / $countOne
    : $countOne / $countTwo;

In other words, the whole first ... ? ... : ... ... ? ... : ... expression is evaluated first, and when the second one runs, it's working with one of these three possibilities:

 $number = 0.0 ? $countTwo / $countOne : $countOne / $countTwo;
 $number = true ? $countTwo / $countOne : $countOne / $countTwo;
 $number = false ? $countTwo / $countOne : $countOne / $countTwo;

All of these will evaluate either $countTwo / $countOne or $countOne / $countTwo , so risk triggering the division by zero error.

This is a problem of Operator Precedence.

Of course I should tell that this is not very clean code. Anyway Try to use parentheses after semicolons in alternative option;

$number =
    (!$countOne || !$countTwo) ?
        0.0 :
            (($countOne > $countTwo) ?
                $countTwo / $countOne :
                    $countOne / $countTwo);

The reason it doesn't work is that PHP handles ternary operator associativity differently than you might expect. From the manual :

// ternary operator associativity differs from C/C++
$a = true ? 0 : true ? 1 : 2; // (true ? 0 : true) ? 1 : 2 = 2

Thus your expression gets computed as

((! $countOne || ! $countTwo) ? 0.0 : ($countOne > $countTwo)) ?
   $countTwo / $countOne : $countOne / $countTwo;

=>

0.0 ? $countTwo / $countOne : $countOne / $countTwo;

=>

$countOne / $countTwo

hence you get the divide by 0 error. You need to manually group the second operator to make it work properly ie

(! $countOne || ! $countTwo) ?
        0.0 :
            (($countOne > $countTwo) ?
                $countTwo / $countOne :
                    $countOne / $countTwo); 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM