Остаток от деления с fmod

282
23 декабря 2017, 06:48

Необходимо проверять кратность количества и коэффициента. Казалось, остаток от деления должен быть 0, но нет, выводит некоторое значение.

Почему? Как сделать, чтобы в таких случаях корректно считал?

Сейчас сделал временное решение с round(fmod,5).

$count = 46;
$k = 4.60;
echo fmod($count, $k);

Ответ 3.5527136788005E-15

Answer 1

Сегодня разбирали похожий случай с другой функцией и в другом языке, но имеющий причиной, фактически, то же самое: Непонятный результат при системном разделителе «точка»

Как показали комментарии ниже, предложенный мною ранее вариант не дает 100%-ного профита. Тогда, с учётом всех комментариев, наверное, как-то так (прототип нашел у себя в include):

<?php
abstract class FloatNumber {
    public static function isNull($num, $precision = 0.0000000001)
    {
        $precision = abs($precision);
        return -$precision < (float)$num && (float)$num < $precision;
    }
    public static function isEqual($num1, $num2)
    {
        return self::isZero($num1 - $num2);
    }
    public static function fmod($num1, $num2)
    {
        $rest = fmod($num1, $num2);
        if (self::isEqual($rest, $num2)) {
            return 0.0;
        }
        if (mb_strpos($num1, ".") === false) {
            $decimals1 = 0;
        } else {
            $decimals1 = mb_strlen($num1) - mb_strpos($num1, ".") - 1;
        }
        if (mb_strpos($num2, ".") === false) {
            $decimals2 = 0;
        } else {
            $decimals2 = mb_strlen($num2) - mb_strpos($num2, ".") - 1;
        }
        return (float)round($rest, max($decimals1, $decimals2));
    }
}
?>
Answer 2

Проблема связана с тем, как устроены числа с плавающей запятой. Начать с того, что 46 в двоичной системе может быть представлено точно, в то время как 4,6 — не может, и в действительности хранится как 4,59999999999999964472863211995.

В вашем конкретном случае речь идёт об одной операции деления. Операции в стандарте IEEE 754 разработаны так, чтобы не вносить ошибку больше, чем половина ULP. (Это не относится к вычитанию, но углубляться не буду, поскольку здесь не вычитание). Значит, после единственного деления ошибка может отличаться от 0.0, но будет меньше, чем ULP/2.

ULP (Unit in the Last Place) — единица в последнем знаке. Это не константа, она отличается для разных чисел, поэтому мы будем обозначать её ULP(x), то есть это функция от числа x.

Нам надо посчитать ULP для остатка от деления 46 на 4,6. Поскольку результат сравним c 4,6 (то есть находится в диапазоне от 0 до 4,6) мы как раз хотим посчитать ULP(4,6).

Двоичное представление числа 4,6 равно 1,00100110011001100110011001100110011001100110011001102×22. У чисел с двойной точностью под мантиссу выделено 52 бита, следовательно ULP(4.6) равно 0,00000000000000000000000000000000000000000000000000012×22. Константа слева от знака умножения называется машинным эпсилоном, она равна 2-52.

Показатель степени 2 в формуле 22 — двоичная экспонента числа 4,6. В C/C++ для её вычисления можно вызывать функцию frexp, но в PHP её нет, так что надо заменить на floor(log10($x)/log10(2)).

Сводим всё воедино.

При вычислении остатка от деления $n на $m ошибка может составить ULP($m)/2.

$epsilon = pow(2, -53);
$binaryExponent = floor(log10(abs($m))/log10(2));
$ulp_m = $epsilon = pow(2, $binaryExponent);

Вычислив остаток, сравниваем его с величиной ULP/2. Если он меньше, значит принимаем его равным нулю.

$remainder = fmod($n, $m);
if (abs($remainder) < $ulp_m/2) {
    $remainder = 0.0;
}

Я расставил в коде вызовы abs, чтобы он работал и с отрицательными числами тоже.

Если вам нужно просто вывести число, забудьте всё, что написано выше и просто ограничьте количество выводимых цифр.

sprintf('%.1f', fmod($n, $m))
READ ALSO
Рекурсия PHP и фреймворк Laravel

Рекурсия PHP и фреймворк Laravel

Здравствуйте, имеется следующий код, но код отрабатывает только для третьего уровня вложенности, как заставить его работать до неограниченного...

251
Как проверить подключение к БД в MODx revo

Как проверить подключение к БД в MODx revo

Как проверить подключение к удаленной БД на MODx revo, если подключаюсь через redbeanphp (ORM)

242
Проверить 100к url взятых из txt файла на 404 php

Проверить 100к url взятых из txt файла на 404 php

Есть большое количество urlНеобходимо проверить, какие ещё существуют, а какие нет

208
Где найти файл tempale.php в Bitrix

Где найти файл tempale.php в Bitrix

Приветствую! Подскажите, новичку в Bitrix, где найти файл tempalephp? Я с этой CMS раньше не работала

319