#php #double #cpu
Необходимо проверять кратность количества и коэффициента. Казалось, остаток от деления должен быть 0, но нет, выводит некоторое значение. Почему? Как сделать, чтобы в таких случаях корректно считал? Сейчас сделал временное решение с round(fmod,5). $count = 46; $k = 4.60; echo fmod($count, $k); Ответ 3.5527136788005E-15
Ответы
Ответ 1
Сегодня разбирали похожий случай с другой функцией и в другом языке, но имеющий причиной, фактически, то же самое: Непонятный результат при системном разделителе «точка» Как показали комментарии ниже, предложенный мною ранее вариант не дает 100%-ного профита. Тогда, с учётом всех комментариев, наверное, как-то так (прототип нашел у себя в include):Ответ 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))
Комментариев нет:
Отправить комментарий