如何从浮动中舍入,格式化和有条件地修剪零?

时间:2018-03-13 22:08:25

标签: php floating-point conditional number-formatting decimal-point

我编写了一个自定义函数,必须调用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;
}

3 个答案:

答案 0 :(得分:2)

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

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

规则,据我所知:

  1. number_format()必须是$number上的第一次操作,以便执行舍入并保持输出准确性。
  2. 您的小数长度上限为5
  3. 当传递的$decimals参数大于2时,可以删除尾随零,直到只剩下两位数。
  4. 注意,因为number_format()的输出将是一个无法作为数字AND处理的字符串,因为修剪过程需要几个步骤来评估和执行,我将使用单个正则表达式模式来减少代码膨胀

    代码:(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
    }
    

    当然,如果您想申报一次性变量,可以提取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
    

    根据所需的效果,此模式仅在适当/适用的情况下删除尾随零。它不会零添加到字符串的右侧。

答案 1 :(得分:0)

我们可以在控制带小数参数的位数的同时实现您想要的结果。

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测试类。您可以添加自己的测试以确保您想要的所有案例都工作(这也需要使用phpunit,但这是一个完整的其他主题)。

答案 2 :(得分: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

非常感谢大家。