如何从浮点数中舍入、格式化和有条件地 trim 零?
How to round, format, and conditionally trim zeros from floats?
我编写了一个自定义函数,它必须调用 number_format()
将我的输入数字(浮点数)四舍五入到预定的小数位数(小数位数应上限为 5)并对小数点进行修改字符和千位定界符。
数字四舍五入和格式化后,我也希望有条件地trim一些小数值的尾随零。
具体来说,如果输入值要四舍五入到小数点后 0、1 或 2 位,则不需要 trim 零。但是,如果舍入到 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;
}
我们可以在使用 decimals 参数控制位数的同时达到您想要的结果。
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, ' ')), ' ');
}
如果您有兴趣,这里是我用来创建此文件的 gist,以及一个 phpunit 测试 class。您可以添加自己的测试以确保您想要的所有情况都能正常工作(这也需要使用 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
非常感谢大家。
我已经重写了我的解决方案,并确认它与您发布的带有示例字符串的解决方案一样准确。
如果我的解决方案在您的项目中失败,请提供新的示例数据来揭示问题。
规则,据我了解:
number_format()
必须是对 $number
的第一个操作,以便执行舍入并保持输出精度。
- 您的小数长度有 hard-coded 上限,即
5
。
- 当传递的
$decimals
参数大于 2
时,可以删除尾随零,直到只剩下两位数。
请注意,由于 number_format()
的输出将是一个无法作为数字处理的字符串,并且因为修整过程需要多个步骤来评估和执行,所以我将使用一个正则表达式模式来减少代码膨胀。
代码:(Demo)
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
}
当然,如果你想声明single-use变量,你可以去掉min()
和preg_quote()
调用;或者,如果你喜欢超长 one-liners:
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
根据预期效果,此模式将仅删除 个尾随零,其中appropriate/applicable。它不会 在字符串的右侧添加 个零。
我编写了一个自定义函数,它必须调用 number_format()
将我的输入数字(浮点数)四舍五入到预定的小数位数(小数位数应上限为 5)并对小数点进行修改字符和千位定界符。
数字四舍五入和格式化后,我也希望有条件地trim一些小数值的尾随零。
具体来说,如果输入值要四舍五入到小数点后 0、1 或 2 位,则不需要 trim 零。但是,如果舍入到 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;
}
我们可以在使用 decimals 参数控制位数的同时达到您想要的结果。
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, ' ')), ' ');
}
如果您有兴趣,这里是我用来创建此文件的 gist,以及一个 phpunit 测试 class。您可以添加自己的测试以确保您想要的所有情况都能正常工作(这也需要使用 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
非常感谢大家。
我已经重写了我的解决方案,并确认它与您发布的带有示例字符串的解决方案一样准确。
如果我的解决方案在您的项目中失败,请提供新的示例数据来揭示问题。
规则,据我了解:
number_format()
必须是对$number
的第一个操作,以便执行舍入并保持输出精度。- 您的小数长度有 hard-coded 上限,即
5
。 - 当传递的
$decimals
参数大于2
时,可以删除尾随零,直到只剩下两位数。
请注意,由于 number_format()
的输出将是一个无法作为数字处理的字符串,并且因为修整过程需要多个步骤来评估和执行,所以我将使用一个正则表达式模式来减少代码膨胀。
代码:(Demo)
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
}
当然,如果你想声明single-use变量,你可以去掉min()
和preg_quote()
调用;或者,如果你喜欢超长 one-liners:
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
根据预期效果,此模式将仅删除 个尾随零,其中appropriate/applicable。它不会 在字符串的右侧添加 个零。