Как округлить, отформатировать и условно обрезать нули от поплавков?

Я написал пользовательскую функцию, которая должна вызывать number_format() округлить введенное число (число с плавающей запятой) до заданного числа десятичных знаков (количество десятичных знаков должно быть ограничено цифрой 5) и внести изменения в десятичный знак и разделитель тысяч.

После того как число округлено и отформатировано, я также хочу условно обрезать некоторые конечные нули из десятичного значения.

В частности, если входное значение должно быть округлено до 0, 1 или 2 десятичных разрядов, то обрезка нуля не требуется. Однако, если округление до 3 или более мест, то конечные нули могут быть удалены, пока не останутся только два десятичных знака.

Вот матрица тестовых случаев и мой желаемый результат:

                |      D    E    C    I    M    A    L      P    L    A    C    E    S
INPUT      |   0   |    1    |     2    |     3     |     4      |      5      |       6
----------------+-------+---------+----------+-----------+------------+-------------+------------
1.01            |     1 |     1,0 |     1,01 |     1,01  |     1,01   |     1,01    |     1,01
----------------+-------+---------+----------+-----------+------------+-------------+------------
1.044           |     1 |     1,0 |     1,04 |     1,044 |     1,044  |     1,044   |     1,044
----------------+-------+---------+----------+-----------+------------+-------------+------------
0.240000        |     0 |     0,2 |     0,24 |     0,24  |     0,24   |     0,24    |     0,24
----------------+-------+---------+----------+-----------+------------+-------------+------------
0.200           |     0 |     0,2 |     0,20 |     0,20  |     0,20   |     0,20    |     0,20
----------------+-------+---------+----------+-----------+------------+-------------+------------
0.24540         |     0 |     0,2 |     0,25 |     0,245 |     0,2454 |     0,2454  |     0,2454
----------------+-------+---------+----------+-----------+------------+-------------+------------
0.24546         |     0 |     0,2 |     0,25 |     0,245 |     0,2455 |     0,24546 |     0,24546
----------------+-------+---------+----------+-----------+------------+-------------+------------
6500.00         | 6 500 | 6 500,0 | 6 500,00 | 6 500,00  | 6 500,00   | 6 500,00    | 6 500,00
----------------+-------+---------+----------+-----------+------------+-------------+------------
3760.54         | 3 761 | 3 760,5 | 3 760,54 | 3 760,54  | 3 760,54   | 3 760,54    | 3 760,54
----------------+-------+---------+----------+-----------+------------+-------------+------------
3760.000000000  | 3 760 | 3 760,0 | 3 760,00 | 3 760,00  | 3 760,00   | 3 760,00    | 3 760,00
----------------+-------+---------+----------+-----------+------------+-------------+------------
0.04000         |     0 |     0,0 |     0,04 |     0,04  |     0,04   |     0,04    |     0,04
----------------+-------+---------+----------+-----------+------------+-------------+------------
1.000000        |     1 |     1,0 |     1,00 |     1,00  |     1,00   |     1,00    |     1,00
----------------+-------+---------+----------+-----------+------------+-------------+------------
1.000005        |     1 |     1,0 |     1,00 |     1,00  |     1,00   |     1,00001 |     1,00001

Это мой текущий код:

function format($number, $decimal=0, $thousands=' ')
{
$number = number_format($number, $decimal, '.', '');

$whole    = floor($number);
$fraction = $number - $whole;

if(!(int)$fraction && $decimal > 2) {
$decimal = 2;
}

if (false !== strpos($number, '.')) {
$number = rtrim(rtrim($number, '0'), '.');
}

$number = number_format($number, $decimal, '.', $thousands);

return $number;
}

0

Решение

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

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

Правила, как я их понимаю

  1. number_format() ДОЛЖЕН быть первой манипуляцией на $number так что выполняется округление и поддерживается точность вывода.
  2. У вас есть жестко заданный верхний предел десятичной длины, который 5,
  3. Когда прошло $decimals параметр больше чем 2, допускается удаление конечных нулей, пока не останется только две цифры.

Обратите внимание, потому что выход из number_format() будет строкой, которая не может быть обработана как число И, потому что процесс обрезки требует нескольких шагов для оценки и выполнения, я буду использовать один шаблон регулярных выражений, чтобы уменьшить раздувание кода.

Код: (демонстрация)

function format($number, $decimals = 2, $dec_point = ',', $thousands_sep = ' ') {
$number = number_format($number, min(5, $decimals), $dec_point, $thousands_sep);  // round to decimal max
return preg_replace("~".preg_quote($dec_point, '~')."\d{".min(2, $decimals)."}[^0]*\K0+$~", '', $number);  // trim zeros (if any, beyond minimum allowance) from right side of decimal character
}

Конечно, вы можете вытащить min() а также preg_quote() вызывает, если вы хотите объявить одноразовые переменные; или, альтернативно, если вам нравятся причудливо длинные однострочники:

function format($number, $decimals = 2, $dec_point = ',', $thousands_sep = ' ') {
return preg_replace("~".preg_quote($dec_point, '~')."\d{".min(2, $decimals)."}[^0]*\K0+$~", '', number_format($number, min(5, $decimals), $dec_point, $thousands_sep));
}

Разбивка паттернов:

"                       #double-quote-wrap the expression to allow variables to be parsed
~                       #pattern delimiter
".preg_quote($dec_point, '~')."  #match the (escaped when necessary) value of `$dec_point`match the value declared as $dec_point
\d{".min(2, $decimals)."}        #match any two digits (if $decimals<2 then use $decimals else use 2 as "quantifier)
[^0]*                   #match zero or more non-zero characters (could have also used [1-9])
\K                      #restart the fullstring match (release/forget all previously matched characters
0+                      #match one or more occurrences of the character: 0
$                       #match the end of the string
~                       #pattern delimiter
"                       #close double-quoted expression

Согласно желаемому эффекту, эта модель будет только Удалить конечные нули, где это уместно / применимо. Я не буду добавлять нули с правой стороны строки.

2

Другие решения

Мы можем достичь желаемых результатов, контролируя количество цифр с помощью параметра десятичных дробей.

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

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

// note: I assume PHP >= 7.1

echo format('3760.541234', 2);
// "3 760,54"
function format(string $number, int $decimal = 0): string
{
if (strpos($number, '.') === false) {
return makeNumber($number);
}

[$whole, $fraction] = explode('.', $number);

return makeNumber($whole) . ($fraction > 0 ? (',' . makeDecimals($fraction, $decimal)) : '');
}

function makeDecimals(string $decimals, int $limit): string
{
if (\strlen($decimals) === 0) {
throw new \LogicException('we are formatting an empty string');
}
return substr(rtrim($decimals, '0'), 0, $limit);
}

function makeNumber(string $number): string
{
return ltrim(strrev(chunk_split(strrev($number), 3, ' ')), ' ');
}

Если вы заинтересованы, вот суть файла, который я использовал для создания этого, вместе с тестовым классом phpunit. Вы можете добавить свои собственные тесты, чтобы убедиться, что все случаи, которые вы хотите, работают (для этого также необходимо использовать phpunit, но это совсем другая тема).

0

Сегодня утром я решил это.

    function format($number, $decimals=2, $dec_point = ',', $thousands_sep = ' ') {

$decimals = (int)$decimals;

if($decimals > 5) {
$decimals = 5; //Maximum decimals limit..
}

$number = number_format((float)$number, $decimals, $dec_point, $thousands_sep);

if(strpos($number, $dec_point) === false) {
return $number; //No decimal values..
}

$splArr = explode($dec_point, $number);
$split = array_reverse(str_split($splArr[1]));

foreach($split as $cleanNum)
{
if($decimals > 2 && $cleanNum === '0') { //Keep minimum two decimals.
$number = substr($number, 0, -1);
$decimals--;
} else {
break; //Found a non zero number.
}
}

return rtrim($number,$dec_point);
}

$test = [
'1.01',
'1.044',
'0.240000',
'0.200',
'0.24540',
'0.24546',
'6500.00',
'3760.54',
'3760.000000000',
'0.04000',
'1.000000'
];

foreach ($test as $number) {
echo format($number, 6).PHP_EOL;
}

Результат:

1,01
1,044
0,24
0,20
0,2454
0,24546
6 500,00
3 760,54
3 760,00
0,04
1,00

Большое спасибо всем.

0
По вопросам рекламы [email protected]