Domanda

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?

È stato utile?

Soluzione

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)

Altri suggerimenti

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

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

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.

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);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top