【问题标题】:handling decimal precision in Mysql or PHP在 Mysql 或 PHP 中处理小数精度
【发布时间】:2012-07-20 00:14:06
【问题描述】:


我有一个 MySQL 表,即estimate_item,其中包含几列保存具有固定精度的十进制值。这是我的表结构。

表名:estimate_item

在使用 PHP 检索记录时,我使用 MySQL 的函数进行了一些基本计算,我使用的 SQL 查询是这样的。

SELECT 
    COUNT(ei.item_id) as total_item,
    SUM(ei.quantity) as total_quantity,
    SUM(ei.number_of_carton) as total_carton,
    SUM(ei.number_of_carton * ei.cbm_per_carton) as total_cbm,
    SUM(ei.quantity * ei.cost) as total_cost,
    SUM(ei.quantity * ei.rate) as total_rate,
    e.commission_amount,
    SUM(ei.quantity * ei.rate) + e.commission_amount as total_amount
FROM
    ai_estimate e
LEFT JOIN
    ai_estimate_item ei ON ei.estimate_id = e.id
WHERE
    ei.estimate_id = ?

例如上面的查询返回结果。

+------------+----------------+--------------+-----------+------------+------------+-------------------+--------------+
| total_item | total_quantity | total_carton | total_cbm | total_cost | total_rate | commission_amount | total_amount |
+------------+----------------+--------------+-----------+------------+------------+-------------------+--------------+
|          2 |            120 |          807 | 1122.6440 |     2.7500 |   137.5000 |           1500.00 |    1637.5000 |
+------------+----------------+--------------+-----------+------------+------------+-------------------+--------------+

由于以下列包含 4 个精度值total_cbm_total, total_cost, total_rate, total_amount,我想以这种方式对所有四列的值进行四舍五入。

a) 如果值为1122.6440,则将其四舍五入为1122.644

b) 如果值为2.7500,则将其四舍五入为2.75

c) 如果值为137.5000,则将其四舍五入为137.50

d) 如果值为1200.0000,则将其四舍五入为1200.00

即我希望这些值包含至少两个精度,根据值可以达到 3 或 4 个精度。

有什么方法可以直接在 MySQL 中执行此操作吗?还是 PHP 方式?

感谢和问候。

【问题讨论】:

  • 根据您给出的示例,您要做的就是截断尾随零。如果你想在 PHP 中输​​出它们,你可以使用字符串操作或 number_format 函数。
  • 如果我此时有值1200.0000,则截断尾随零可能是一个问题,我想要两个尾随零,因此值应该是1200.00,不是吗?

标签: php mysql decimal


【解决方案1】:

数据库方面,请注意列 cost、rate 和 cmb_per_carton 的尾随 0 的数量是由表结构预定义的,在您的情况下是 4 个 0:

cost DECIMAL 19,4
rate DECIMAL 19,4
cmb_per_carton DECIMAL 19,4

如果您对这些字段进行操作,结果自然会有相同的尾随 0。

PHP 方面是我刚刚破解的一个简单函数,它将完成您正在寻找的功能:

function getDecimalFormatted($value, $minPrecision) {
    $value = (float) $value;
    return ((round((($value - floor($value))*pow(10, 5)))%1000) > 0) ? $value : number_format($value, $minPrecision, '.', '');
}

使用示例:

$value = "1234.71200";
echo getDecimalFormatted($value, 2)."\n<br>";

$value = "1234.70000";
echo getDecimalFormatted($value, 2)."\n<br>";

这个输出:

1234.712
1234.70

【讨论】:

    【解决方案2】:

    一种方法是使用正则表达式删除小数点后数字末尾的所有0,然后格式化一个带两个小数位的数字并比较两个结果 - 返回最长的一个(按字符串) .虽然这可以在 mysql 中完成,但在 php 中会更容易:

    //$input - number to format
    //$num - minimum number of decimal places to keep
    function reformat($input, $num) {
        $parse = preg_replace('/(\.[1-9]*)0+$/', "$1", $input);
        $withmin = number_format($parse, 2); 
        return strlen($withmin) < strlen($parse) ? $parse : $withmin;
    }
    

    【讨论】:

      【解决方案3】:

      如果您在 MySQL 中这样做,我的猜测是您需要过度复杂化查询添加 CASE WHEN...END。

      所以我会在 PHP 中这样做。

      function reprecise($value)
      {
          $two_digit = number_format($value, 2);
          $three_digit = number_format($value, 3);
          return (float)$two_digit == (float)$three_digit ? $two_digit : $three_digit;
      }
      

      这使用了一个技巧——首先它计算两位和三位数字的表示,然后检查它们是否是相同的浮点数。如果是,则三位版本的最后一位必须为零,并使用两位版本。

      要扩展到 2、3 或 4 位,您可以在一个循环中完成:

      function reprecise($value)
      {
          $digits = 4;
          for ($rep = number_format($value, $digits--); $digits >= 2; $digits--)
          {
                  $new = number_format($value, $digits);
                  if ($new != $rep)
                          break;
                  $rep = $new;
          }
          return $rep;
      }
      

      或者,因为字符串函数很可能比 number_format 更快:

      function reprecise($value)
      {
          $digits = 9;
          $base   = strrev(number_format($value, $digits));
          for ($i = 0; $i < $digits-2; $i++)
                  if ($base[$i] != '0')
                          break;
          return strrev(substr($base, $i));
      }
      

      【讨论】:

      • 不错的方法。当然,随着最大小数位数的增加,它会变得越来越复杂。事实上,OP 正在处理 2、3 或 4 位小数。如果你推断,比如小数点后 2 到 10 位之间的任何地方,这将变得有点难以管理。
      • 是的。现在编辑答案。一种更有效的方法是生成具有最大长度的字符串,然后对其进行 strrev() 并开始删除最多 ($digits-2) 个连续零,然后再次对其进行 strrev()。
      • 哦,好吧,我想我不够快。无论如何检查我的版本,我认为我的(几乎)单行很整洁:)
      猜你喜欢
      • 1970-01-01
      • 2018-03-13
      • 1970-01-01
      • 2016-03-07
      • 2014-05-20
      • 1970-01-01
      • 1970-01-01
      • 2019-01-11
      • 1970-01-01
      相关资源
      最近更新 更多