php_nub_qq
php_nub_qq

Reputation: 16065

PHP: Elegant way to avoid division by zero

Much like this site, my current project has reputation and in the script that I'm working on I need to calculate the ratio between two users' reputations.

$attacker->ratio = $defender->rep / $attacker->rep;
$defender->ratio = $attacker->rep / $defender->rep;

In doing so I may occasionally end up with the divisor's reputation being 0, which sucks!

Obviously I can add a couple of checks, but I was wondering if a prettier solution hasn't been invented, something like @ infront, but I know that's not a good idea..

Upvotes: 19

Views: 31079

Answers (9)

Ner
Ner

Reputation: 153

We faced the same issue on our legacy PHP Application. Our team fixes it with two functions:

    function ensure_num($val): float|int
    {
        if (is_numeric($val)) {
            return (float) $val;
        }

        return 0;
    }

    function x_divide(mixed $num1, mixed $denominator): float
    {
        $num1 = ensure_num($num1);
        $denominator = ensure_num($denominator);
        if ($denominator == 0) {
            return 0;
        }

        return $num1/$denominator;
    }

Then you don't need to worry anymore about this warning on PHP version 8.0 and possible a fatal error on version 8.3.

To use it, just replace:

$var1 = $num1/$num2;

to:

$var1 = x_divide($num1, $num2);

Using the above makes your legacy to not throw any issue anymore.

Upvotes: 0

For prevent Error Message use Try

     try {
    return $a/$b;
  } catch (DivisionByZeroError $e) {
    echo 'Error DivisionByZeroErro';
  }

Upvotes: 0

Ryan Vincent
Ryan Vincent

Reputation: 4513

Assuming positive numbers are valid then ensure the lowest divisor value will be 1.

$defender->ratio = $attacker->rep / max($defender->rep, 1);

// --------------------------------------------

suggested code by someone else,

@php_nub_qq suggested alternate code...
In today's php

$defender->ratio = $attacker->rep / ($defender->rep ?? 1);

Alas, this code provided by @php_nub_qq does not work in PHP 7.4 ;-( see @OceanBt in the comments... I thank them for the correction! :)

so, Here I am maintaining code that I never was interested in. And now, is shown to be PHP version specific! Here is the correction...

$y = 100/($x ?: 1);

Why am I doing this?

  1. Notice my code still works fine! Avoid 'clever features' for production code.
  2. Because someone believes that have a 'better answer' doesn't mean they do!

I don't mind doing this maintenance of the code of someone else! This is the real world! We have to do this. I posted it because:
I really am trying to help programmers to learn.

// My thoughts about the 'improvement' to what I posted...

imo, that suggestion of yours isn't the same as my approach! I specifically used 'max' as it forces a limit on a range of numbers. You can nest the 'min' and 'max' functions also to force a limited range.

Your method is a 'selection' and not why I did the answer I did. :)

Upvotes: 47

matiit
matiit

Reputation: 8017

This is the way I would do it.

$defender->ratio = ($defender->rep === 0) ? 0 : $attacker->rep / $defender->rep;

Upvotes: 13

RedSparr0w
RedSparr0w

Reputation: 468

If you are using PHP > 5.3 you could use the ternary operator ?:

$attacker->ratio = $defender->rep / ($attacker->rep ?: 1);
$defender->ratio = $attacker->rep / ($defender->rep ?: 1);

This means if the variable before the operator is false or empty the value afterwards would be returned otherwise the first value is returned

Example below:

$attacker->rep = 0;
$defender->rep = 55;
$attacker->ratio = $defender->rep / ($attacker->rep ?: 1); // returns 55 / 1 = 55
$defender->ratio = $attacker->rep / ($defender->rep ?: 1); // returns 0 / 55 = 0

Upvotes: 10

Lumen
Lumen

Reputation: 3574

I don’t recommend to change any user’s reputation artificially solely to make the math work. Instead you can do this:

function calc_rep_ratio($self, other)
{
    if ($self->rep != 0) {
        return $other->rep / $self->rep;
    } else {
        return NAN;
    }
}

Then use the function

$defender->ratio = calc_rep_ratio($defender, $attacker);
$attacker->ratio = calc_rep_ratio($attacker, $defender);

In the presentation, you can check for the number

if (is_nan($user->ratio)) {
    echo 'No ratio available';
} else {
    echo $user->ratio;
}

Upvotes: 1

Useless Intern
Useless Intern

Reputation: 1302

Based upon the other answers I'll assume you only update the ratio if it's not zero (Remember you asked for elegance not clarity):

if( !empty($attacker->rep) ) { 
    $attacker->ratio = $defender->rep / $attacker->rep;
}

PHP treats 0 as empty.

Upvotes: 2

beerwin
beerwin

Reputation: 10347

Use a ternary operator to check if the divider is zero or not.

$attacker->ratio = $attacker->rep > 0 ? $defender->rep / $attacker->rep : 1;
$defender->ratio = $defender->rep > 0 ? $attacker->rep / $defender->rep : 1;

Use whatever you wish instead of the value 1 as default.

Upvotes: 2

user399666
user399666

Reputation: 19909

Something like this?

if($defender->rep != 0){
    $attacker->ratio = $defender->rep / $attacker->rep;
}

Upvotes: 4

Related Questions