【问题标题】:How to round, format, and conditionally trim zeros from floats?如何从浮点数中舍入、格式化和有条件地修剪零?
【发布时间】:2018-08-22 07:50:16
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

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

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

    规则,据我所知:

    1. number_format() 必须是对 $number 的第一个操作,以便执行舍入并保持输出精度。
    2. 您有一个硬编码的十进制长度上限,即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
    }
    

    当然,如果你想声明一次性变量,你可以去掉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
    

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

    【讨论】:

    • 感谢您的回答。您的代码每保留两位小数。所以,如果十进制值大于两位,我必须保留两位小数。如果没有小数,我需要对数字进行四舍五入。 3v4l.org/vLks1
    • @Dersy 我已经调整了我的答案,就像你的答案的功能一样。
    • 是的,完全覆盖。 @Lawrence 希望 OP 能够看到这种清晰程度对于未来的问题很重要。
    【解决方案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, ' ')), ' ');
    }
    

    如果您有兴趣,这里是我用来创建此文件的gist,以及一个 phpunit 测试类。您可以添加自己的测试以确保您想要的所有案例都有效(这也需要使用 phpunit,但这是另一个主题)。

    【讨论】:

    • 3v4l.org/aH89V 你的方法不像round()number_format() php.net/manual/en/function.chunk-split.php#67173 和@Lawrence 请取消删除你的答案。
    • @mickmackusa 是的,它没有。为什么?
    • 这似乎是 OP 问题的必要功能——其中最大十进制值是函数参数。如果数字具有比最大小数允许的更多(保留)十进制值,则您的方法将截断而不是四舍五入。
    • OP 的问题并不清楚,不,这是一个要求。如果他们明确表示,我会很乐意做出改变。
    • 很公平。我认为这个问题可能不应该在问题得到澄清之前得到回答……由 OP
    【解决方案3】:

    今天早上我解决了。

        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
    

    非常感谢大家。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-27
      • 2020-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-13
      相关资源
      最近更新 更多