dewd
dewd

Reputation: 4429

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

Upvotes: 1

Views: 96

Answers (3)

IMSoP
IMSoP

Reputation: 97688

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.

Upvotes: 3

Nick
Nick

Reputation: 147146

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 i.e.

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

Upvotes: 2

staskrak
staskrak

Reputation: 863

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);

Upvotes: 2

Related Questions