PHP ceil дает неправильные результаты, если ввод представляет собой число с плавающей запятой без десятичной точки

Я боролся с функцией PHP ceil(), что дало мне немного неправильные результаты - рассмотрим следующее:

$num = 2.7*3; //float(8.1)
$num*=10; //float(81)
$num = ceil($num); //82, but shouldn't this be 81??
$num/=10; //float(8.2)

У меня есть число, которое может иметь любое количество знаков после запятой, и мне нужно округлить его до одного знака после запятой. то есть 8.1 должно быть 8.1, 8.154 должно быть 8.2, а 8 должно быть оставлено как 8.

Как я достиг этого, так это взять число, умножить его на 10, ceil(), а затем разделить на десять, но, как вы можете видеть, в некоторых случаях я получаю дополнительную 0,1.

Может ли кто-нибудь сказать мне, почему это происходит, и как это исправить?

Любая помощь очень ценится

РЕДАКТИРОВАТЬ: было +=10 вместо *=10: S

РЕДАКТИРОВАТЬ 2: я прямо не упомянул об этом, но мне нужно, чтобы десятичная дробь ВСЕГДА округлялась ВВЕРХ, а не вниз - этот ответ наиболее близок:

rtrim(rtrim(sprintf('%.1f', $num), '0'), '.');

Однако округляет 3,84 до 3,8, когда мне нужно 3,9. Извините, это не было яснее :(

Окончательное редактирование:

В итоге я сделал следующее:

$num = 2.7*3; //float(8.1)
$num*=10; //float(81)
$num = ceil(round($num, 2)); //81 :)
$num/=10; //float(8.1)

Который работает :)


person totallyNotLizards    schedule 19.10.2011    source источник


Ответы (6)


Это более чем вероятно из-за ошибки с плавающей запятой.

Возможно, вам повезет попробовать эту процедуру вместо этого.

<?php
$num = 2.7*3;
echo rtrim(rtrim(sprintf('%.1f', $num), '0'), '.');
person erisco    schedule 19.10.2011
comment
это дает 2 десятичных знака, мне нужно 1 - этот код работает: echo rtrim(rtrim(sprintf('%.1f', $num), '0'), '.'); в остальном отличный ответ, спасибо - person totallyNotLizards; 19.10.2011

Поплавки могут быть непостоянной вещью. Не все действительные числа могут быть правильно представлены конечным числом двоичных разрядов.
Как оказалось, десятичная часть 0,7 является одним из таких чисел (получается 0,10 с бесконечностью, повторяющей "1100" после нее). В итоге вы получите число, которое чуть больше 0,7, поэтому, когда вы умножаете на 10, вы получаете цифру единицы чуть выше 7.

Что вы можете сделать, так это сделать проверку на вменяемость. Возьмите цифру с плавающей запятой и вычтите ее целочисленную форму. Если результирующее значение меньше, скажем, 0,0001, считайте это внутренней ошибкой округления и оставьте как есть. Если результат больше 0,0001, обычно применяется ceil().

Редактировать: забавный пример, который вы можете сделать, если вы используете Windows, чтобы показать это, — открыть встроенное приложение калькулятора. Введите «4», затем примените функцию квадратного корня (с x ^ y, где y = 0,5). Вы увидите, что он правильно отображает «2». Теперь вычтите из него 2, и вы увидите, что в результате у вас не будет 0. Это вызвано внутренними ошибками округления, когда он пытался вычислить квадратный корень из 4. При отображении числа 2 ранее он знал, что эти очень далекие конечные цифры, вероятно, были ошибкой округления, но когда это все, что осталось, он получает немного запутался.

(Прежде чем кто-нибудь расскажет мне об этом, я понимаю, что это слишком упрощенно, но, тем не менее, я считаю это достойным примером.)

person Mr. Llama    schedule 19.10.2011

Проблема в том, что числа с плавающей запятой РЕДКО такие, какими вы их ожидаете. Ваш 2.7 * 3, вероятно, будет выглядеть примерно как 81.00000000000000000001, что ceil() до 82. Для такого рода вещей вам придется обернуть вызовы ceil/round/floor некоторыми проверками точности, чтобы обрабатывать эти дополнительные микроскопические различия.

person Marc B    schedule 19.10.2011

Используйте %f вместо %.1f.

echo rtrim(rtrim(sprintf('%f', $num), '0'), '.');
person თორნიკე ბაბუნაძე    schedule 22.05.2015

Почему бы не попробовать это:

$num = 2.7*3; 
$num *= 100; 
$num = floor($num); 
$num /= 10; 
$num = ceil($num); 
$num /= 10;
person vinum    schedule 25.06.2015

Преобразуйте свой номер в строку и завершите строку.

function roundUp($number, $decimalPlaces){
    $multi = pow(10, $decimalPlaces);
    $nrAsStr = ($number * $multi) . "";
    return ceil($nrAsStr) / $multi;
}
person Triggsy    schedule 10.02.2021