Я написал пользовательскую функцию, которая должна вызывать 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;
}
Я переписал свое решение и подтвердил, что оно будет таким же точным, как и ваше опубликованное решение с предоставленными образцами строк.
Если мое решение окажется неудачным в вашем проекте, предоставьте новые образцы данных, которые выявят проблему.
Правила, как я их понимаю
number_format()
ДОЛЖЕН быть первой манипуляцией на $number
так что выполняется округление и поддерживается точность вывода.5
,$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
Согласно желаемому эффекту, эта модель будет только Удалить конечные нули, где это уместно / применимо. Я не буду добавлять нули с правой стороны строки.
Мы можем достичь желаемых результатов, контролируя количество цифр с помощью параметра десятичных дробей.
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, но это совсем другая тема).
Сегодня утром я решил это.
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
Большое спасибо всем.