You Old Fool
You Old Fool

Reputation: 22959

Determine if $x is divisible evenly by $y in PHP

I simply want to know if $x is evenly divisible by $y. For example's sake assume:

$x = 70;
$y = .1;

First thing I tried is:

$x % $y

This seems to work when both numbers are integers but fails if they are not and if $y is a decimal less than 1 returns a "Division by zero" error, so then I tried:

fmod($x,$y)

Which returns equally confusing results, "0.099999999999996".

php.net states fmod():

Returns the floating point remainder of dividing the dividend (x) by the divisor (y)

Well according to my calculator 70 / .1 = 700. Which means the remainder is 0. Can someone please explain what I'm doing wrong?

Upvotes: 9

Views: 2540

Answers (5)

neal
neal

Reputation: 33

As you said, using the modulus operator works fine when it's an integer, so why not set it up so that it operates on integers. In my case, I needed to check divisibility by 0.25:

$input = 5.251
$x = round($input, 3); // round in case $input had more decimal places
$y = .25;
$result = ($x * 1000) % ($y * 1000);

In your case:

$input = 70.12
$x = round($input, 2);
$y = .1;
$result = ($x * 100) % ($y * 100);

Upvotes: 0

Tyler
Tyler

Reputation: 1837

.1 doesn't have an exact representation in binary floating point, which is what causes your incorrect result. You could multiply them by a large enough power of 10 so they are integers, then use %, then convert back. This relies on them not being different by a big enough factor that multiplying by the power of 10 causes one of them to overflow/lose precision. Like so:

$x = 70;
$y = .1;
$factor = 1.0;
while($y*$factor != (int)($y*$factor)){$factor*=10;}
echo ($x*$factor), "\n";
echo ($y*$factor), "\n";
echo (double)(($x*$factor) % ($y*$factor))/$factor;

Upvotes: 6

markcial
markcial

Reputation: 9333

There is a pure math library in bitbucket : https://bitbucket.org/zdenekdrahos/bn-php

The solution will be then :

php > require_once 'bn-php/autoload.php';
php > $eval = new \BN\Expression\ExpressionEvaluator();
php > $operators = new \BN\Expression\OperatorsFactory();
php > $eval->setOperators($operators->getOperators(array('%')));
php > echo $eval->evaluate('70 % 0.1'); // 0
0.00000000000000000000

tested on php5.3

credits : http://www.php.net/manual/en/function.bcmod.php#111276

Upvotes: 4

TimWolla
TimWolla

Reputation: 32711

One solution would be doing a normal division and then comparing the value to the next integer. If the result is that integer or very near to that integer the result is evenly divisible:

$x = 70;
$y = .1;

$evenlyDivisable = abs(($x / $y) - round($x / $y, 0)) < 0.0001;

This subtracts both numbers and checks that the absolute difference is smaller than a certain rounding error. This is the usual way to compare floating point numbers, as depending on how you got a float the representation may vary:

php> 0.1 + 0.1 + 0.1 == 0.3
bool(false)
php> serialize(.3)
'd:0.29999999999999999;'
php> serialize(0.1 + 0.1 + 0.1)
'd:0.30000000000000004;'

See this demo:

php> $x = 10;
int(10)
php> $y = .1;
double(0.1)
php> abs(($x / $y) - round($x / $y, 0)) < 0.0001;
bool(true)
php> $y = .15;
double(0.15)
php> abs(($x / $y) - round($x / $y, 0)) < 0.0001;
bool(false)

Upvotes: 10

hanleyhansen
hanleyhansen

Reputation: 6437

Float-point representation varies from machine to machine. Thankfully there are standards. PHP typically uses the IEEE 754 double precision format for floating-point representation which is one of the most common standards. See here for more information on that. With that said take a look at this calculator for a better understanding as to the why. As for the how I like Tim's solution especially if you're dealing with user input.

Upvotes: 0

Related Questions