如何从浮点数中舍入、格式化和有条件地 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

非常感谢大家。

我已经重写了我的解决方案,并确认它与您发布的带有示例字符串的解决方案一样准确。

如果我的解决方案在您的项目中失败,请提供新的示例数据来揭示问题。

规则,据我了解:

  1. number_format() 必须是对 $number 的第一个操作,以便执行舍入并保持输出精度。
  2. 您的小数长度有 hard-coded 上限,即 5
  3. 当传递的 $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。它不会 在字符串的右侧添加 个零。