PHP 8.3.0 RC 6 available for testing

money_format

(PHP 4 >= 4.3.0, PHP 5, PHP 7)

money_format将数字格式化成货币字符串

警告

本函数已自 PHP 7.4.0 起弃用,自 PHP 8.0.0 起移除。强烈建议不要依赖本函数。

说明

money_format(string $format, float $number): string

money_format() 返回格式化好的 number 版本。 此函数包装了 C 函数库中的 strfmon(),不同之处是:此实现每次只能转化一个数字。

参数

format

格式字符串由以下几部分组成:

  • 单个 % 字符

  • 可选的标记(flags)

  • 可选的字段宽度

  • 可选的,左侧精度

  • 可选的,右侧精度

  • 必选的,单个转化字符

标记(Flags)

可选多个标记,分别是:

=f

字符:=,并紧跟一个字符(单字节) f,用于数字填充。默认的填充字符是空格。

^

禁用分组字符(比如金额中的逗号。在本地区域设置 locale 中定义)。

+ or (

正负数字的格式。使用 +,将使用区域设置(locale)中相当于 +- 的符号。 如果使用 (,负数将被圆括号围绕。不设置的话,默认为 +

!

不输出货币符号(比如 ¥)。

-

有这个符号的时候,将使字段左对齐(填充到右边),默认是相反的,是右对齐的(填充到左边)。

字段宽度

w

十进制数值字符串的宽度。字段将右对齐,除非使用了 - 标记。默认值 0。

左侧精度

#n

小数字符(比如小数点)前的最大位数 (n)。 常用于同一列中的格式对齐。 如果位数小于 n 则使用填充字符填满。 如果实际位数大于 n,此设置将被忽略。

如果没用 ^ 标识禁用分组,分组分隔符会在添加填充字符之前插入(如果有的话)。 分组分隔符不会应用到填充字符里,哪怕填充字符是个数字。

为了保证对齐,出现在之前或者之后的字符,都会填充必要的空格,保证正负情况下长度都一样。

右侧精度

.p

小数点后的一段数字 (p)。 如果 p 的值是 0(零),小数点右侧的数值将被删除。 如果不使用这个标记,默认展现取决于当前的区域设置。 小数点后指定位数的数字,四舍五入格式化。

转化字符

i

根据国际化区域设置中的货币格式,格式化数值。(比如,locale 是 USA:USD 1,234.56)。

n

根据国际化区域设置中国家的货币格式,格式化数值。(比如,locale 是 de_DE:EU1.234,56)。

%

返回字符 %

number

需要格式化的数字。

返回值

返回格式化后的字符。格式字符串前后的字符将原封不动返回。 传入的 number 如果不是数字,将返回 null 并且产生 E_WARNING

更新日志

版本 说明
7.4.0 弃用此函数。使用 NumberFormatter::formatCurrency() 替代。

示例

示例 #1 money_format() 例子

使用不同的 locale 和格式字符串,来说明此函数的用法。

<?php

$number
= 1234.56;

// 让我们打印 en_US locale 的国际化格式
setlocale(LC_MONETARY, 'en_US');
echo
money_format('%i', $number) . "\n";
// USD 1,234.56

// 意大利国家的格式,带两位浮点小数`
setlocale(LC_MONETARY, 'it_IT');
echo
money_format('%.2n', $number) . "\n";
// Eu 1.234,56

// 负数的使用
$number = -1234.5672;

// 美国国家的格式,使用圆括号 () 标记负数。
// 左侧精度使用十位
setlocale(LC_MONETARY, 'en_US');
echo
money_format('%(#10n', $number) . "\n";
// ($ 1,234.57)

// 相似的格式,添加了右侧两位小数点的精度,同时用 * 来填充
echo money_format('%=*(#10.2n', $number) . "\n";
// ($********1,234.57)

// 让我们左对齐,14位宽,左侧八位,右侧两位,不带分组字符
// de_DE 的国际化格式
setlocale(LC_MONETARY, 'de_DE');
echo
money_format('%=*^-14#8.2i', 1234.56) . "\n";
// Eu 1234,56****

// 让我们在格式字符串前后,添加一些简介
setlocale(LC_MONETARY, 'en_GB');
$fmt = 'The final value is %i (after a 10%% discount)';
echo
money_format($fmt, 1234.56) . "\n";
// The final value is GBP 1,234.56 (after a 10% discount)

?>

注释

注意:

具有 strfmon 的系统才有 money_format() 函数。 例如 Windows 不具备,所以 Windows 系统上 money_format() 未定义。

注意:

locale 设置中, LC_MONETARY 会影响此函数的行为。 在使用函数前,首先要用 setlocale() 来设置合适的区域设置(locale)。

参见

add a note

User Contributed Notes 16 notes

up
63
tim
8 years ago
For most of us in the US, we don't want to see a "USD" for our currency symbol, so '%i' doesn't cut it. Here's what I used that worked to get what most people expect to see for a number format.

$number = 123.4
setlocale(LC_MONETARY, 'en_US.UTF-8');
money_format('%.2n', $number);

output:
$123.40

That gives me a dollar sign at the beginning, and 2 digits at the end.
up
35
Rafael M. Salvioni
14 years ago
This is a some function posted before, however various bugs were corrected.

Thank you to Stuart Roe by reporting the bug on printing signals.

<?php
/*
That it is an implementation of the function money_format for the
platforms that do not it bear.

The function accepts to same string of format accepts for the
original function of the PHP.

(Sorry. my writing in English is very bad)

The function is tested using PHP 5.1.4 in Windows XP
and Apache WebServer.
*/
function money_format($format, $number)
{
$regex = '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?'.
'(?:#([0-9]+))?(?:\.([0-9]+))?([in%])/';
if (
setlocale(LC_MONETARY, 0) == 'C') {
setlocale(LC_MONETARY, '');
}
$locale = localeconv();
preg_match_all($regex, $format, $matches, PREG_SET_ORDER);
foreach (
$matches as $fmatch) {
$value = floatval($number);
$flags = array(
'fillchar' => preg_match('/\=(.)/', $fmatch[1], $match) ?
$match[1] : ' ',
'nogroup' => preg_match('/\^/', $fmatch[1]) > 0,
'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ?
$match[0] : '+',
'nosimbol' => preg_match('/\!/', $fmatch[1]) > 0,
'isleft' => preg_match('/\-/', $fmatch[1]) > 0
);
$width = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
$left = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
$right = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
$conversion = $fmatch[5];

$positive = true;
if (
$value < 0) {
$positive = false;
$value *= -1;
}
$letter = $positive ? 'p' : 'n';

$prefix = $suffix = $cprefix = $csuffix = $signal = '';

$signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
switch (
true) {
case
$locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+':
$prefix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+':
$suffix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+':
$cprefix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+':
$csuffix = $signal;
break;
case
$flags['usesignal'] == '(':
case
$locale["{$letter}_sign_posn"] == 0:
$prefix = '(';
$suffix = ')';
break;
}
if (!
$flags['nosimbol']) {
$currency = $cprefix .
(
$conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
$csuffix;
} else {
$currency = '';
}
$space = $locale["{$letter}_sep_by_space"] ? ' ' : '';

$value = number_format($value, $right, $locale['mon_decimal_point'],
$flags['nogroup'] ? '' : $locale['mon_thousands_sep']);
$value = @explode($locale['mon_decimal_point'], $value);

$n = strlen($prefix) + strlen($currency) + strlen($value[0]);
if (
$left > 0 && $left > $n) {
$value[0] = str_repeat($flags['fillchar'], $left - $n) . $value[0];
}
$value = implode($locale['mon_decimal_point'], $value);
if (
$locale["{$letter}_cs_precedes"]) {
$value = $prefix . $currency . $space . $value . $suffix;
} else {
$value = $prefix . $value . $space . $currency . $suffix;
}
if (
$width > 0) {
$value = str_pad($value, $width, $flags['fillchar'], $flags['isleft'] ?
STR_PAD_RIGHT : STR_PAD_LEFT);
}

$format = str_replace($fmatch[0], $value, $format);
}
return
$format;
}

?>
up
18
todoventas at xarxa-cat dot net
9 years ago
In Rafael M. Salvioni function localeconv(); returns an invalid array in my Windows XP SP3 running PHP 5.4.13 so to prevent the Warning Message: implode(): Invalid arguments passed i just add the $locale manually. For other languages just fill the array with the correct settings.

<?

$locale = array(
'decimal_point' => '.',
'thousands_sep' => '',
'int_curr_symbol' => 'EUR',
'currency_symbol' => '€',
'mon_decimal_point' => ',',
'mon_thousands_sep' => '.',
'positive_sign' => '',
'negative_sign' => '-',
'int_frac_digits' => 2,
'frac_digits' => 2,
'p_cs_precedes' => 0,
'p_sep_by_space' => 1,
'p_sign_posn' => 1,
'n_sign_posn' => 1,
'grouping' => array(),
'mon_grouping' => array(0 => 3, 1 => 3)

);
?>
up
17
jeremy
15 years ago
If money_format doesn't seem to be working properly, make sure you are defining a valid locale. For example, on Debian, 'en_US' is not a valid locale - you need 'en_US.UTF-8' or 'en_US.ISO-8559-1'.

This was frustrating me for a while. Debian has a list of valid locales at /usr/share/i18n/SUPPORTED; find yours there if it's not working properly.
up
9
jsb17NO at SPAMcornell dot edu
10 years ago
To drop zero value decimals, use the following:
<?php
/*
Same as php number_format(), but if ends in .0, .00, .000, etc... , drops the decimals altogether
Returns string type, rounded number - same as php number_format()):
Examples:
number_format_drop_zero_decimals(54.378, 2) ==> '54.38'
number_format_drop_zero_decimals(54.00, 2) ==> '54'
*/
function number_format_drop_zero_decimals($n, $n_decimals)
{
return ((
floor($n) == round($n, $n_decimals)) ? number_format($n) : number_format($n, $n_decimals));
}
?>
Results:
number_format_drop_zero_decimals(54.377, 2) ==> 54.38
number_format_drop_zero_decimals('54.377', 2) ==> 54.38
number_format_drop_zero_decimals(54.377, 3) ==> 54.377
number_format_drop_zero_decimals(54.007, 2) ==> 54.01
number_format_drop_zero_decimals(54.000, 2) ==> 54
number_format_drop_zero_decimals(54.00, 2) ==> 54
number_format_drop_zero_decimals(54.0, 2) ==> 54
number_format_drop_zero_decimals(54.1, 2) ==> 54.10
number_format_drop_zero_decimals(54., 2) ==> 54
number_format_drop_zero_decimals(54, 2) ==> 54
number_format_drop_zero_decimals(54, 3) ==> 54
number_format_drop_zero_decimals(54 + .13, 2) ==> 54.13
number_format_drop_zero_decimals(54 + .00, 2) ==> 54
number_format_drop_zero_decimals(54.0007, 4) ==> 54.0007
number_format_drop_zero_decimals(54.0007, 3) ==> 54.001
number_format_drop_zero_decimals(54.00007, 3) ==> 54 // take notice
up
8
~B
11 years ago
We found that after switching from Ubuntu 10.04 php -v 5.3.2, to Ubuntu 12.04 php -v 5.3.10 this no longer worked:

<?php setlocale(LC_MONETARY, 'en_US'); ?>

Found that using:

<?php setlocale(LC_MONETARY, 'en_US.UTF-8'); ?>

worked find
up
3
Felix Duterloo
7 years ago
Improvement to Rafael M. Salvioni's solution for money_format on Windows: when no currency symbol is selected, in the formatting, the minus sign was also lost when the locale puts it in position 3 or 4. Changed $currency = ''; to: $currency = $cprefix .$csuffix;

function money_format($format, $number) {
$regex = '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?' .
'(?:#([0-9]+))?(?:\.([0-9]+))?([in%])/';
if (setlocale(LC_MONETARY, 0) == 'C') {
setlocale(LC_MONETARY, '');
}
$locale = localeconv();
preg_match_all($regex, $format, $matches, PREG_SET_ORDER);
foreach ($matches as $fmatch) {
$value = floatval($number);
$flags = array(
'fillchar' => preg_match('/\=(.)/', $fmatch[1], $match) ?
$match[1] : ' ',
'nogroup' => preg_match('/\^/', $fmatch[1]) > 0,
'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ?
$match[0] : '+',
'nosimbol' => preg_match('/\!/', $fmatch[1]) > 0,
'isleft' => preg_match('/\-/', $fmatch[1]) > 0
);
$width = trim($fmatch[2]) ? (int) $fmatch[2] : 0;
$left = trim($fmatch[3]) ? (int) $fmatch[3] : 0;
$right = trim($fmatch[4]) ? (int) $fmatch[4] : $locale['int_frac_digits'];
$conversion = $fmatch[5];

$positive = true;
if ($value < 0) {
$positive = false;
$value *= -1;
}
$letter = $positive ? 'p' : 'n';

$prefix = $suffix = $cprefix = $csuffix = $signal = '';

$signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
switch (true) {
case $locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+':
$prefix = $signal;
break;
case $locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+':
$suffix = $signal;
break;
case $locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+':
$cprefix = $signal;
break;
case $locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+':
$csuffix = $signal;
break;
case $flags['usesignal'] == '(':
case $locale["{$letter}_sign_posn"] == 0:
$prefix = '(';
$suffix = ')';
break;
}
if (!$flags['nosimbol']) {
$currency = $cprefix .
($conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
$csuffix;
} else {
$currency = $cprefix .$csuffix;
}
$space = $locale["{$letter}_sep_by_space"] ? ' ' : '';

$value = number_format($value, $right, $locale['mon_decimal_point'], $flags['nogroup'] ? '' : $locale['mon_thousands_sep']);
$value = @explode($locale['mon_decimal_point'], $value);

$n = strlen($prefix) + strlen($currency) + strlen($value[0]);
if ($left > 0 && $left > $n) {
$value[0] = str_repeat($flags['fillchar'], $left - $n) . $value[0];
}
$value = implode($locale['mon_decimal_point'], $value);
if ($locale["{$letter}_cs_precedes"]) {
$value = $prefix . $currency . $space . $value . $suffix;
} else {
$value = $prefix . $value . $space . $currency . $suffix;
}
if ($width > 0) {
$value = str_pad($value, $width, $flags['fillchar'], $flags['isleft'] ?
STR_PAD_RIGHT : STR_PAD_LEFT);
}

$format = str_replace($fmatch[0], $value, $format);
}
return $format;
}
up
5
andrey.dobrozhanskiy [-a-t-] gmail com
13 years ago
This function divides integer value by commas. F.e.

<?php
echo formatMoney(1050); # 1,050
echo formatMoney(1321435.4, true); # 1,321,435.40
echo formatMoney(10059240.42941, true); # 10,059,240.43
echo formatMoney(13245); # 13,245

function formatMoney($number, $fractional=false) {
if (
$fractional) {
$number = sprintf('%.2f', $number);
}
while (
true) {
$replaced = preg_replace('/(-?\d+)(\d\d\d)/', '$1,$2', $number);
if (
$replaced != $number) {
$number = $replaced;
} else {
break;
}
}
return
$number;
}
?>
up
3
richard dot selby at uk dot clara dot net
17 years ago
Double check that money_format() is defined on any version of PHP you plan your code to run on. You might be surprised.

For example, it worked on my Linux box where I code, but not on servers running BSD 4.11 variants. (This is presumably because strfmon is not defined - see note at the top of teis page). It's not just a windows/unix issue.
up
0
Anonymous
1 month ago
Rafael M. Salvioni's code has a small bug in it when the value is positive and the format provided contains a ( flag. The value should only be surrounded in parenthesis when the value is negative. This should fix it:

<?php
if (!function_exists('money_format'))
{
function
money_format($format, $number)
{
$regex = '/%((?:[\^!\-]|\+|\(|\=.)*)([0-9]+)?'.
'(?:#([0-9]+))?(?:\.([0-9]+))?([in%])/';
if (
setlocale(LC_MONETARY, 0) == 'C') {
setlocale(LC_MONETARY, '');
}
$locale = localeconv();
preg_match_all($regex, $format, $matches, PREG_SET_ORDER);
foreach (
$matches as $fmatch) {
$value = floatval($number);
$flags = array(
'fillchar' => preg_match('/\=(.)/', $fmatch[1], $match) ?
$match[1] : ' ',
'nogroup' => preg_match('/\^/', $fmatch[1]) > 0,
'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ?
$match[0] : '+',
'nosimbol' => preg_match('/\!/', $fmatch[1]) > 0,
'isleft' => preg_match('/\-/', $fmatch[1]) > 0
);
$width = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
$left = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
$right = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
$conversion = $fmatch[5];

$positive = true;
if (
$value < 0) {
$positive = false;
$value *= -1;
}
$letter = $positive ? 'p' : 'n';

$prefix = $suffix = $cprefix = $csuffix = $signal = '';

$signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
switch (
true) {
case
$locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+':
$prefix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+':
$suffix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+':
$cprefix = $signal;
break;
case
$locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+':
$csuffix = $signal;
break;
case
$flags['usesignal'] == '(' && !$positive:
case
$locale["{$letter}_sign_posn"] == 0:
$prefix = '(';
$suffix = ')';
break;
}
if (!
$flags['nosimbol']) {
$currency = $cprefix .
(
$conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
$csuffix;
} else {
$currency = '';
}
$space = $locale["{$letter}_sep_by_space"] ? ' ' : '';

$value = number_format($value, $right, $locale['mon_decimal_point'],
$flags['nogroup'] ? '' : $locale['mon_thousands_sep']);
$value = @explode($locale['mon_decimal_point'], $value);

$n = strlen($prefix) + strlen($currency) + strlen($value[0]);
if (
$left > 0 && $left > $n) {
$value[0] = str_repeat($flags['fillchar'], $left - $n) . $value[0];
}
$value = implode($locale['mon_decimal_point'], $value);
if (
$locale["{$letter}_cs_precedes"]) {
$value = $prefix . $currency . $space . $value . $suffix;
} else {
$value = $prefix . $value . $space . $currency . $suffix;
}
if (
$width > 0) {
$value = str_pad($value, $width, $flags['fillchar'], $flags['isleft'] ?
STR_PAD_RIGHT : STR_PAD_LEFT);
}

$format = str_replace($fmatch[0], $value, $format);
}
return
$format;
}
}
?>
up
1
swapnet
15 years ago
Consider formatting currency for some South Asian countries that use ##,##,###.## money format.
The following code generates something like Rs. 4,54,234.00 and so on.

<?php
function convertcash($num, $currency){
if(
strlen($num)>3){
$lastthree = substr($num, strlen($num)-3, strlen($num));
$restunits = substr($num, 0, strlen($num)-3); // extracts the last three digits
$restunits = (strlen($restunits)%2 == 1)?"0".$restunits:$restunits; // explodes the remaining digits in 2's formats, adds a zero in the beginning to maintain the 2's grouping.

$expunit = str_split($restunits, 2);
for(
$i=0; $i<sizeof($expunit); $i++){
$explrestunits .= (int)$expunit[$i].","; // creates each of the 2's group and adds a comma to the end
}

$thecash = $explrestunits.$lastthree;
} else {
$thecash = $convertnum;
}

return
$currency.$thecash.".00"; // writes the final format where $currency is the currency symbol.
}
?>

now call the function as convertcash($row['price'], 'Rs '); // that's the price from the database I called using an Indian Rupees prefix where the price has to be a plain number format, say something like 454234.
up
0
phpdeveloperbalaji at gmail dot com
12 years ago
Hi,

For South Asian Currencies, this function could be a handy one.

It will handle negative as well as float(Paise).

<?php
function my_money_format($number)
{
if(
strstr($number,"-"))
{
$number = str_replace("-","",$number);
$negative = "-";
}

$split_number = @explode(".",$number);

$rupee = $split_number[0];
$paise = @$split_number[1];

if(@
strlen($rupee)>3)
{
$hundreds = substr($rupee,strlen($rupee)-3);
$thousands_in_reverse = strrev(substr($rupee,0,strlen($rupee)-3));
for(
$i=0; $i<(strlen($thousands_in_reverse)); $i=$i+2)
{
$thousands .= $thousands_in_reverse[$i].$thousands_in_reverse[$i+1].",";
}
$thousands = strrev(trim($thousands,","));
$formatted_rupee = $thousands.",".$hundreds;

}
else
{
$formatted_rupee = $rupee;
}

if((int)
$paise>0)
{
$formatted_paise = ".".substr($paise,0,2);
}

return
$negative.$formatted_rupee.$formatted_paise;

}
?>

Thanks,
up
-1
kaigillmann at googlemail dot com
9 years ago
If you get "EUR" instead of the euro symbol, set the locale to utf8 charset like this:

<?php
setlocale
(LC_MONETARY, 'de_DE.utf8');
echo
money_format('%+n', 1234.56);
?>
up
-1
scot from ezyauctionz.co.nz
16 years ago
This is a handy little bit of code I just wrote, as I was not able to find anything else suitable for my situation.
This will handle monetary values that are passed to the script by a user, to reformat any comma use so that it is not broken when it passes through an input validation system that checks for a float.

It is not foolproof, but will handle the common input as most users would input it, such as 1,234,567 (outputs 1234567) or 1,234.00 (outputs 1234.00), even handles 12,34 (outputs 12.34), I expect it would work with negative numbers, but have not tested it, as it is not used for that in my situation.

This worked when other options such as money_format() were not suitable or possible.

<?php
///////////////
// BEGIN CODE convert all price amounts into well formatted values
function converttonum($convertnum,$fieldinput){
$bits = explode(",",$convertnum); // split input value up to allow checking

$first = strlen($bits[0]); // gets part before first comma (thousands/millions)
$last = strlen($bits[1]); // gets part after first comma (thousands (or decimals if incorrectly used by user)

if ($last <3){ // checks for comma being used as decimal place
$convertnum = str_replace(",",".",$convertnum);
}
else{
// assume comma is a thousands seperator, so remove it
$convertnum = str_replace(",","",$convertnum);
}

$_POST[$fieldinput] = $convertnum; // redefine the vlaue of the variable, to be the new corrected one
}

@
converttonum($_POST[inputone],"inputone");
@
converttonum($_POST[inputtwo],"inputtwo");
@
converttonum($_POST[inputthree],"inputthree");
// END CODE
//////////////

?>

This is suitable for the English usage, it may need tweaking to work with other types.
up
-4
sainthyoga2003 at gmail dot com
3 years ago
Be aware, since PHP 7.3 this method is deprecated and from PHP 7.4 this launch a deprecated error, so, setup your PHP web server to untrack E_DEPRECATED error reporting.
up
-5
justsomeone
6 years ago
Using the money_format function with float values which have more than two decimal digits can result in rounding errors.
Maybe this function will help you to avoid these failures:

<?php
// A product with a base price of 12.95
$price = 1295;

// The quantity is also an integer but translated it would be 11.91
$quantity = 1191;

// Result: 154.2345
// It's the same like 12.95 * 11.91
$sum = ($price / 100) * ($quantity /100);

// Wrong result: 154.23
money_format('%!i', $sum);

// Wrong result: 154.23
number_format($sum, 2);

// Wrong result: 154.23
bcmul($price / 100, $quantity / 100, 2);

// Correct result : 154.24
money_format_rounded('%!i', $sum);

/**
* Formats a number as a currency string. Rounds every decimal digit to a defined precision on its own.
*
* @param string $format The format for the money_format function
* @param float|int|string $number The number to be formatted
* @param int $maxPrecision Round up to the $maxPrecision number of decimal digit. Default is: 2
* @param int $roundingType Rounding type for the round function. Default is: \PHP_ROUND_HALF_UP
*
* @return string
*/
function money_format_rounded($format, $number, $maxPrecision = 2, $roundingType = \PHP_ROUND_HALF_UP)
{
$strlen = strlen($number);
if (
$strlen === 0) {
return
money_format($format, $number);
}

$length = $strlen - strrpos($number, '.') - 1;
if (
$length <= 0) {
return
money_format($format, $number);
}

$rounded = $number;
for (
$i = --$length; $i >= $maxPrecision; $i--) {
$rounded = round($rounded, $i, $roundingType);
}

return
money_format($format, $rounded);
}
To Top