Ответ 1
floor
будет делать по вашему желанию.
floor(45.8976 * 100) / 100;
Вы не найдете более прямой функции для этого, так как это нечто странное. Обычно вы будете математически исправлены. Из любопытства - для чего вам это нужно?
Когда число с плавающей запятой необходимо усечь до определенной цифры после числа с плавающей запятой, оказывается, что это сделать нелегко. Например, если усечение должно быть выполнено до второй цифры после точки, цифры должны быть
45.8976 => 45.89, 0.0185 => 0.01
(вторая цифра после точки не округляется в соответствии с третьей цифрой после точки).
Такие функции, как round()
, number_format()
, sprintf()
округляют число и распечатывают
45.8976 => 45.90, 0.0185 => 0.02
Я встретил два решения, и мне интересно, достаточно ли они хороши и какое из них лучше использовать
1.
function truncNumber( $number, $prec = 2 )
{
return bccomp( $number, 0, 10 ) == 0 ? $number : round( $number - pow( 0.1, bcadd( $prec, 1 ) ) * 5, $prec );
}
2.
function truncNumber($number, $prec = 2 )
{
return sprintf( "%.".$prec."f", floor( $number*pow( 10, $prec ) )/pow( 10, $prec ) );
}
floor
будет делать по вашему желанию.
floor(45.8976 * 100) / 100;
Вы не найдете более прямой функции для этого, так как это нечто странное. Обычно вы будете математически исправлены. Из любопытства - для чего вам это нужно?
это мое решение:
/**
* @example truncate(-1.49999, 2); // returns -1.49
* @example truncate(.49999, 3); // returns 0.499
* @param float $val
* @param int f
* @return float
*/
function truncate($val, $f="0")
{
if(($p = strpos($val, '.')) !== false) {
$val = floatval(substr($val, 0, $p + 1 + $f));
}
return $val;
}
function truncate($value)
{
if ($value < 0)
{
return ceil((string)($value * 100)) / 100;
}
else
{
return floor((string)($value * 100)) / 100;
}
}
Почему PHP не обрабатывает математические формулы так, как мы ожидали бы, например. floor(10.04 * 100) = 1003
, когда мы все знаем, что это должно быть 1004
.
Эта проблема не только в PHP, но и во всех langauges, в зависимости от относительной ошибки используемого langauge.
PHP использует формат двойной точности IEEE 754, который имеет относительную погрешность около 1.11e-16. (ресурс)
Реальная проблема заключается в том, что функция floor передает значение float в значение int, например. (int)(10.04 * 100) = 1003
, как мы видели ранее в функции пола. (ресурс)
Итак, чтобы преодолеть эту проблему, мы можем применить float к строке, строка может точно представлять любое значение, тогда функция floor будет точно отличать строку.
Чтобы усечь числа, нужно использовать "лучшее" (я взял здесь номер, который работает для моего примера):
$number = 120,321314;
$truncate_number = number_format($number , 1); // 120,3
$truncate_number = number_format($number , 2); // 120,32
$truncate_number = number_format($number , 3); // 120,321
Надеюсь, что эта помощь легка, чем другие ответы здесь, но это легко только для случаев, в которых она работает. Вот пример, в котором он не работает (demo):
$number = 10.046;
echo number_format($number , 2); // 10.05
Функция number_format сложна, вы можете решить свою проблему таким образом (с php.net):
Чтобы предотвратить округление, которое происходит, когда следующая цифра после последнего значащего десятичного знака равна 5 (упоминается несколькими людьми ниже):
<?php
function fnumber_format($number, $decimals='', $sep1='', $sep2='') {
if (($number * pow(10 , $decimals + 1) % 10 ) == 5)
$number -= pow(10 , -($decimals+1));
return number_format($number, $decimals, $sep1, $sep2);
}
$t=7.15;
echo $t . " | " . number_format($t, 1, '.', ',') . " | " . fnumber_format($t, 1, '.', ',') . "\n\n";
//result is: 7.15 | 7.2 | 7.1
$t=7.3215;
echo $t . " | " . number_format($t, 3, '.', ',') . " | " . fnumber_format($t, 3, '.', ',') . "\n\n";
//result is: 7.3215 | 7.322 | 7.321
} ?>
Хотя этот вопрос старый, я отправлю еще один ответ, если кто-то наткнется на эту проблему, как я. Простое решение для положительных чисел:
function truncate($x, $digits) {
return round($x - 5 * pow(10, -($digits + 1)), $digits);
}
Я тестировал его на основе строкового решения с 10 миллионами случайных дробей, и они всегда соответствовали. Он не демонстрирует точность, которую делают решения на основе floor
.
Расширение его для нулей и негативов довольно просто.
Чтобы сделать это точно для обоих + ve и -ve номеров, вам необходимо использовать:
- функция php floor()
для + ve чисел
- функция php ceil()
для чисел -ve
function truncate_float($number, $decimals) {
$power = pow(10, $decimals);
if($number > 0){
return floor($number * $power) / $power;
} else {
return ceil($number * $power) / $power;
}
}
причина этого в том, что floor()
всегда округляет число вниз, а не к нулю.
т.е. floor()
эффективно округляет числа до большего абсолютного значения
например floor(1.5) = 1
, а floor(-1.5) = 2
Поэтому для метода усечения с помощью multiply by power, round, then divide by power
:
- floor()
работает только для положительных чисел
- ceil()
работает только для отрицательных чисел
Чтобы проверить это, скопируйте следующий код в редактор http://phpfiddle.org/lite (или аналогичный):
<div>Php Truncate Function</div>
<br>
<?php
function truncate_float($number, $places) {
$power = pow(10, $places);
if($number > 0){
return floor($number * $power) / $power;
} else {
return ceil($number * $power) / $power;
}
}
// demo
$lat = 52.4884;
$lng = -1.88651;
$lat_tr = truncate_float($lat, 3);
$lng_tr = truncate_float($lng, 3);
echo 'lat = ' . $lat . '<br>';
echo 'lat truncated = ' . $lat_tr . '<br>';
echo 'lat = ' . $lng . '<br>';
echo 'lat truncated = ' . $lng_tr . '<br><br>';
// demo of floor() on negatives
echo 'floor (1.5) = ' . floor(1.5) . '<br>';
echo 'floor (-1.5) = ' . floor(-1.5) . '<br>';
?>
Функция round()
имеет прецизионный параметр, а также третий параметр для указания метода округления, но вы правы, он не выполняет усечения.
То, что вы ищете, это функции floor()
и ceil()
. Недостатком является то, что у них нет параметра точности, поэтому вам нужно будет сделать что-то вроде этого:
$truncated = floor($value*100)/100;
Я вижу другой способ для этого:
function trunc($float, $prec = 2) {
return substr(round($float, $prec+1), 0, -1);
}
Но это не лучше, чем любой другой... round
тоже можно заменить на sprintf
.
Я хотел бы добавить свой вклад в этот ответ с помощью функции, которую я использую для своих нужд, которая считает усеченные позиции как для отрицательных, так и для положительных значений.
function truncate($val,$p = 0)
{
$strpos = strpos($val, '.');
return $p < 0 ? round($val,$p) : ($strpos ? (float)substr($val,0,$strpos+1+$p) : $val);
}
... Я думаю, что это просто и быстро использовать.
Причиной этого является внутреннее двоичное представление чисел с плавающей запятой. Данное значение сверху 10.04
должно быть представлено внутренне как мощность базы 2.
Потенциальная экспонента для части с плавающей запятой составляет ln(0,04)/ln(2) = -4,64...
. Это уже показывает, что 10.04
не может быть представлено точно как двоичное число. В итоге у нас есть что-то вроде 10.04 ~ 2^3 + 2^1 + 2^-5 + 2^-7 + 2^-11 + ...
с максимальной точностью для части с плавающей запятой.
Именно поэтому 10.04
на самом деле немного меньше и может быть представлен как 10.039...
.
Чтобы обойти это поведение, есть две возможности: либо прятаться непосредственно со строковыми операциями, либо использовать для PHP такую же библиотеку точности, как BcMath
.
<?php
function test($value)
{
// direct string operations
$fromString = (float)preg_replace(
'#(?:(\.\d{1,2})\d*)?$#',
'${1}',
(string)$value
);
// using arbitrary precision
// @see http://php.net/manual/en/function.bcadd.php
$fromBcMath = (float)bcadd(
(string)$value,
'0',
2
);
var_dump($fromString, $fromBcMath);
}
test(3);
test(4.60);
test(10.04);
test(45.8976);
Результат для вышеприведенного кода будет генерировать (я добавил выделение строк для чтения):
double(3)
double(3)
double(4.6)
double(4.6)
double(10.04)
double(10.04)
double(45.89)
double(45.89)
Чтобы преодолеть проблему с floor для позитивов и ceil для негативов, вы можете просто разделить число на единицу и получить целую часть, используя intdiv()
. И проблему с плавающей точкой можно решить с помощью round()
после умножения.
Итак, это должно работать:
<?php
function truncate($number, $precision = 2) {
$prec = 10 ** $precision;
return intdiv(round($number * $prec), 1) / $prec;
}
Выходы:
>>> truncate(45.8976)
=> 45.89
>>> truncate(-45.8976)
=> -45.89
>>> truncate(3)
=> 3
>>> truncate(-3)
=> -3
>>> truncate(10.04)
=> 10.04
>>> truncate(-10.04)
=> -10.04
Поскольку функции BCMath действительно усекаются, мое решение - умножить на 1 с помощью bcmul и преобразовать полученную строку в число с плавающей точкой. Таким образом мы избегаем делений и умножения, которые могут привести к ошибкам округления:
+bcmul(strval(45.8976), '1', 2);
Я использую входные значения в качестве строки, потому что я читаю с использованием чисел с плавающей точкой, BCMath может выводить странные результаты
Вы можете попробовать это:
bcdiv($inputNumber, '1', $precision = 2);