Когда число с плавающей точкой должно быть усечено до определенной цифры после плавающей запятой, получается, что сделать это непросто. Например, если усечение должно быть выполнено до второй цифры после точки, цифры должны быть
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
как мы видели ранее в функции floor. (ресурс)
Поэтому, чтобы преодолеть эту проблему, мы можем применить float к строке, строка может точно представлять любое значение, тогда функция floor будет точно передавать строку в int.
Чтобы усечь числа, нужно использовать «лучшее» (я взял здесь номер, который работает для моего примера):
$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 } ?>
Функция 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($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>'; ?>
Причиной этого является внутреннее двоичное представление чисел с плавающей запятой. Данное значение свыше 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...
Чтобы обойти это поведение, есть две возможности: либо прятаться непосредственно со строковыми операциями, либо использовать библиотеку произвольной точности, такую как BcMath
for PHP.
<?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)
Я хотел бы добавить свой вклад в этот ответ с помощью функции, которую я использую для своих нужд, которая рассматривает усеченные позиции как для отрицательных, так и для положительных значений.
function truncate($val,$p = 0) { $strpos = strpos($val, '.'); return $p < 0 ? round($val,$p) : ($strpos ? (float)substr($val,0,$strpos+1+$p) : $val); }
… Я думаю, что это просто и быстро использовать.