Определите, делится ли $x равномерно на $y в PHP


Я просто хочу знать, если $x равномерно делится на $y. например, предположим:

$x = 70;
$y = .1;

Первое, что я попробовал, это:

$x % $y

Это, кажется, работает, когда оба числа являются целыми числами, но терпит неудачу, если это не так, и если $y десятичное число меньше 1 возвращает ошибку "деление на ноль", поэтому я попытался:

fmod($x,$y)

Который возвращает столь же запутанные результаты, "0.09999999999996".

Php.net состояния fmod():

Возвращает остаток с плавающей запятой от деление дивиденда (x) на делитель (y)

Ну, согласно моему калькулятору 70 / .1 = 700. Что означает, что остаток равен 0. Может кто-нибудь объяснить, что я делаю не так?

5 9

5 ответов:

Одним из решений будет выполнение нормальногоделения , а затем сравнение значения со следующим целым числом. Если результат равен этому целому числу или очень близок к нему, то результат делится равномерно:

$x = 70;
$y = .1;

$evenlyDivisable = abs(($x / $y) - round($x / $y, 0)) < 0.0001;
Это вычитает оба числа и проверяет, что абсолютная разница меньше, чем определенная ошибка округления. Это обычный способ сравнения чисел с плавающей точкой, так как в зависимости от того, как вы получили поплавок, представление может меняться:
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;'

Смотрите это демо:

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)

.1 не имеет точного представления в двоичной системе счисления с плавающей запятой, что и приводит к неверному результату. Вы можете умножить их на достаточно большую степень 10, чтобы они были целыми числами, затем использовать%, а затем преобразовать обратно. Это зависит от того, что они не отличаются достаточно большим фактором, что умножение на степень 10 приводит к переполнению/потере точности одного из них. Вот так:

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

Есть чисто математическая библиотека в системе Bitbucket : https://bitbucket.org/zdenekdrahos/bn-php

Тогда решение будет следующим:

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

Проверено на php5.3

Кредиты: http://www.php.net/manual/en/function.bcmod.php#111276

Представление с плавающей точкой варьируется от машины к машине. К счастью, есть стандарты. PHP обычно использует формат двойной точности IEEE 754 для представления с плавающей запятой, который является одним из наиболее распространенных стандартов. смотрите здесь для получения дополнительной информации об этом. С учетом сказанного взгляните наэтот калькулятор для лучшего понимания того, почемуПочему . Что касается как Мне нравится решение Тима, особенно если вы имеете дело с пользовательским вводом.

Как вы сказали, использование оператора модуля прекрасно работает, когда это целое число, так почему бы не настроить его так, чтобы он работал с целыми числами. В моем случае мне нужно было проверить делимость на 0,25:

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

В вашем случае:

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