Reputation: 4429
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
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
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
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