PHP 的 number_format() 怎么用:数字格式化与小数位设置

PHP 的 number_format() 怎么用:数字格式化与小数位设置

PHP 的 number_format() 怎么用:数字格式化与小数位设置

在 PHP 中展示价格、数量或统计结果时,原始数字往往不够易读。例如,数据库中的 1234567.5 可能需要显示为 1,234,567.50。number_format() 可以将数值转换成带指定小数位和分组分隔符的字符串,常用于页面展示、报表和导出文本。

不过,它只负责格式化,不负责验证输入、精确计算金额,也不会自动处理本地化或 HTML 安全。理解这些边界,才能避免把显示工具误当成计算或安全工具。

基本语法与返回值

<?php
number_format(
    float $num,
    int $decimals = 0,
    string $decimal_separator = ".",
    string $thousands_separator = ","
): string
  • $num:要格式化的数值。
  • $decimals:小数点后保留的位数,默认是 0。
  • $decimal_separator:小数分隔符,默认是英文句点 .。
  • $thousands_separator:整数部分的分组分隔符,默认是逗号 ,。

函数返回的是字符串,不是整数或浮点数。格式化后的结果适合展示,不应该再拿去做数值运算。

<?php
echo number_format(1234567.891);
// 1,234,568

echo number_format(1234567.891, 2);
// 1,234,567.89

省略第二个参数时,小数位默认是 0。传入非零小数位后,函数会按要求舍入,并补足需要的小数位。因此结果可能比输入短,也可能比输入长:

<?php
echo number_format(12.3, 2);
// 12.30

echo number_format(12.345, 2);
// 12.35

设置小数位与分隔符

需要自定义分隔符时,必须传入前面的参数。比如某些地区习惯用逗号作为小数分隔符、句点作为千位分组符:

<?php
echo number_format(1234567.89, 2, ',', '.');
// 1.234.567,89

参数顺序容易写错:第三个参数是小数分隔符,第四个参数才是千位分组符。想用逗号作为小数符、空格作为分组符,可以这样写:

<?php
echo number_format(1234567.89, 2, ',', ' ');
// 1 234 567,89

分组符也可以设为空字符串,表示不进行分组;小数符也可以设为空字符串,不过这通常不适合显示带小数的数字,因为小数部分会与整数部分直接连在一起。

<?php
echo number_format(1234567.89, 2, '.', '');
// 1234567.89

分组不是只在三位数之间简单插入字符的通用国际化机制。number_format() 使用固定的千位分组规则,不会根据用户所在地区自动决定印度式分组等格式。分隔符应由应用明确选择,不能仅凭用户系统语言假定。

舍入行为:它不是截断

number_format() 会按指定小数位舍入,不是直接删除多余位数。例如保留两位时,1.239 会成为 1.24,而不是 1.23。

<?php
echo number_format(1.234, 2);
// 1.23

echo number_format(1.235, 2);
// 1.24

echo number_format(1.999, 2);
// 2.00

其舍入规则是 half up:遇到正好位于中点的值时,按远离零的方向舍入。负数也适用这条规则:

<?php
echo number_format(1.5, 0);
// 2

echo number_format(-1.5, 0);
// -2

这条规则与某些场景所需的银行家舍入、截断或其他舍入规则并不相同。如果业务明确要求特定规则,应先用适合业务的舍入逻辑处理数值,再格式化结果;不要假设 number_format() 能选择舍入模式。它没有舍入模式参数。

负数小数位与 PHP 版本差异

$decimals 通常设为零或正整数。PHP 8.3.0 起,负数表示在小数点左侧按相应位数舍入。例如 -1 表示舍入到十位,-2 表示舍入到百位。PHP 8.3.0 之前,负数小数位会按 0 处理。

<?php
// PHP 8.3.0 及更高版本
echo number_format(12345, -1);
// 12,350

echo number_format(12345, -2);
// 12,300

如果应用需要兼容 PHP 8.2 或更早版本,不要依赖负数小数位实现十位、百位舍入;应显式检查运行环境,或使用单独、可测试的业务逻辑。另一个值得注意的边界是:较旧版本对负数小数位的处理与 PHP 8.3 起不同,升级 PHP 后,既有代码的显示结果可能改变。

整数、浮点数与精度边界

虽然参数说明中的 $num 是浮点数,但日常调用时经常会传入整数或数字字符串。PHP 会按函数参数的类型规则处理可转换的值;无法作为数值使用的输入,在现代 PHP 中可能触发类型错误或产生其他问题,不应把“传给函数试试看”当成输入校验。

特别要留意浮点数的二进制表示。很多十进制小数无法用二进制浮点数精确表示,计算后可能得到接近预期、但略有偏差的值。格式化只是把已有数值舍入成文本,并不能恢复计算过程中已经丢失的精度。

<?php
$value = 0.1 + 0.2;

var_dump($value);
echo number_format($value, 2);
// 通常显示 0.30

这个例子看起来正常,不代表浮点数适合处理所有精确金额。涉及货币、税额、计费或需要可审计精度的运算时,优先考虑以最小货币单位的整数存储和计算,例如用“分”表示人民币金额,或者使用专门的十进制定点数方案。完成精确计算并明确舍入规则后,再把数值格式化成展示文本。

金额示例:整数分转成显示字符串

如果金额始终以整数分存储,可以先拆分出元和分,再格式化整数部分。以下示例适用于非负金额,展示时固定保留两位小数:

<?php
function formatCents(int $cents): string
{
    if ($cents < 0) {
        throw new InvalidArgumentException('此示例只处理非负金额');
    }

    $yuan = intdiv($cents, 100);
    $fen = $cents % 100;

    return number_format($yuan, 0, '.', ',')
        . '.'
        . str_pad((string) $fen, 2, '0', STR_PAD_LEFT);
}

echo formatCents(123456789);
// 1,234,567.89

这个示例避免先把整笔金额转换为二进制浮点数再进行金额运算。实际项目还需按业务决定负数、币种、小数位数以及舍入规则;不同币种的小数位不一定相同,不能一概假设都是两位。

输入校验:格式化不等于验证

不要直接把请求参数、表单内容或数据库中的任意文本交给 number_format(),然后把它当成已验证的数字。应先按业务规则验证输入,再显式转换类型。is_numeric() 可以用于判断值是否为数字或数字字符串,但它接受的形式可能比业务允许的更宽,例如指数记法;它也不会检查金额范围、精度或币种规则。

<?php
$input = $_POST['amount'] ?? '';

if (!is_string($input) || !is_numeric($input)) {
    throw new InvalidArgumentException('金额格式不正确');
}

$amount = (float) $input;

if (!is_finite($amount) || $amount < 0) {
    throw new InvalidArgumentException('金额超出允许范围');
}

echo number_format($amount, 2, '.', ',');

上面只是说明“先校验、再转换、最后格式化”的基本流程,并不适用于所有金额业务。若输入必须是普通十进制数字、禁止指数记法、必须限制小数位或需要防止超大整数溢出,应根据业务定义更严格的格式和范围检查。is_numeric() 本身不会替你完成这些限制。

HTML 输出与安全边界

number_format() 是数字展示函数,不是 HTML 转义函数,也不是安全过滤器。默认分隔符是固定的普通字符,但自定义分隔符如果来自用户输入,就可能包含 HTML 或其他特殊内容。不要因为结果经过了数字格式化,就省略按输出上下文进行的转义。

<?php
$formatted = number_format($amount, 2, '.', ',');

echo '<p>金额:'
    . htmlspecialchars($formatted, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
    . '</p>';

如果分隔符由应用程序固定配置,仍建议在 HTML 输出边界采用一致的转义策略。对 HTML 属性、JavaScript、URL 或 SQL 等其他上下文,应使用对应的安全处理方式,不能用 HTML 转义替代它们。也不要将格式化后的文本用于数据库数值字段或后续算术运算。

与本地化格式化的区别

number_format() 的分隔符由调用者明确传入,不会自动读取操作系统区域设置,也不会提供完整的地区化数字规则。如果应用需要按用户区域显示货币、数字或百分比,可考虑 PHP 的 NumberFormatter(由 intl 扩展提供),按指定地区格式化。

<?php
$formatter = new NumberFormatter('de_DE', NumberFormatter::DECIMAL);
echo $formatter->format(1234567.89);
// 常见结果:1.234.567,89

使用 NumberFormatter 前,应确认部署环境安装并启用了 intl 扩展,并按应用需求处理格式化失败。它与 number_format() 的接口和功能不同;如果只是需要稳定、固定的逗号千分位和两位小数,后者通常更直接。

常见错误与排查方法

错误一:把小数位参数当成最少位数

第二个参数设置的是输出的小数位数,不是“最多保留几位而不补零”。例如 number_format(5, 2) 会输出 5.00。如果需求是去掉末尾多余的零,需要另外定义格式化规则,而不是期待该参数自动省略零。

错误二:把第三、第四个参数写反

参数顺序是小数分隔符在前、千位分组符在后。逗号小数、句点分组应写成 number_format($value, 2, ',', '.'),而不是交换最后两个参数。

错误三:格式化后继续计算

返回值是带分隔符的字符串,例如 1,234.56。它不是通用的数值文本,直接转换回来可能失败,或需要先解析分隔符。正确做法是保留原始数值用于计算,只在需要展示时生成格式化字符串。

错误四:误以为会自动采用用户地区

默认输出采用小数点和逗号分组,不会自动切换到用户本地格式。需要地区化展示时,显式选择分隔符,或使用相应国际化功能。

错误五:用浮点格式化代替金额精确计算

保留两位小数只能规定输出精度,不能解决浮点误差、税务舍入或分摊规则。金额计算应先明确数据表示和业务舍入,再决定如何展示。

错误六:将结果误当成已转义内容

函数不会清除标签、转义特殊字符或阻止脚本注入。凡是写入 HTML 的文本,都应在输出时按上下文使用合适的转义方式;特别不要让外部输入控制分隔符而不做限制。

快速选择:什么时候用它

  • 需要固定小数位、千位分组,且格式规则由应用明确指定:使用 number_format()。
  • 需要随地区变化的数字或货币格式:考虑 NumberFormatter。
  • 需要准确计算金额:用合适的整数或十进制定点数表示进行计算,再格式化展示。
  • 处理用户输入:先验证格式、范围和业务规则,再转换和格式化。
  • 输出到网页:把格式化结果当作普通文本,仍要进行上下文转义。

总结

number_format() 的核心用途很明确:将一个数值转成带指定小数位和分隔符的字符串。默认保留 0 位小数、使用句点作为小数符和逗号作为千位分组符;自定义时记住第三个参数是小数分隔符、第四个参数是千位分组符。它会舍入,不是截断;负数小数位的行为从 PHP 8.3.0 起发生变化;返回值是展示文本,不是精确计算结果,也不是经过安全处理的内容。

只要将校验、计算、格式化和输出转义分开处理,并在需要时考虑 PHP 版本、浮点精度和地区格式,number_format() 就能胜任大多数固定规则的数字展示任务。

© 版权声明

相关文章