Is there an arbitrary-precision alternative to money_format available that could take a string instead of a float as a parameter?
It's not that I plan on doing calculations on trillions of monetary units, but after going through the trouble to properly handle monetary arithmetic without abusing floats, it'd be nice to have a function that doesn't spew random numbers after about 15 digits, even if users decide to give it nonsense data. Or, hey, maybe someone wants to buy two sticks of gum in Zimbabwe dollars?
I hesitate to use regular expressions because I was hoping to make use of the localization of money_format.
edit - found a workable solution; s开发者_运维问答ee below
try the NumberFormatter class
Cobbled together from the commenter-submitted functions on PHP's site here and here. Edited to work with arbitrary-precision parameters.
class format {
function money($format, $number)
{
// Takes plain-format, arbitrary-length decimal string (eg: '123456789123456789.123456')
// Returns localized monetary string, truncated at the hundredth value after the decimal point.
// (eg: $ 123,456,789,123,456,789.12)
$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 = (string) $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 = bcmul($value, '-1');
}
$letter = $positive ? 'p' : 'n';
$prefix = $suffix = $cprefix = $csuffix = $signal = '';
$signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
if ($locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+')
$prefix = $signal;
elseif ($locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+')
$suffix = $signal;
elseif ($locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+')
$cprefix = $signal;
elseif ($locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+')
$csuffix = $signal;
elseif ($flags['usesignal'] == '(' || $locale["{$letter}_sign_posn"] == 0) {
$prefix = '(';
$suffix = ')';
}
if (!$flags['nosimbol']) {
$currency = $cprefix .
($conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
$csuffix;
} else {
$currency = '';
}
$space = $locale["{$letter}_sep_by_space"] ? ' ' : '';
$value = format::number($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;
}
function number ($number , $decimals = 2 , $dec_point = '.' , $sep = ',', $group=3 ){
// Arbitrary-precision number formatting:
// Takes plain-format, arbitrary-length decimal string (eg: '123456789123456789.123456').
// Takes the same parameters as PHP's native number_format plus a flexible 'grouping' parameter.
// WARNINGS: Truncates -- does not round; not inherently locale-aware
$num = (string) $number;
if (strpos($num, '.')) $num = substr($num, 0, (strpos($num, '.') + 1 + $decimals)); // truncate
$num = explode('.',$num);
while (strlen($num[0]) % $group) $num[0]= ' '.$num[0];
$num[0] = str_split($num[0],$group);
$num[0] = join($sep[0],$num[0]);
$num[0] = trim($num[0]);
$num = join($dec_point[0],$num);
return $num;
}
}
Tests:
setlocale(LC_MONETARY, 'en_ZW'); // pick your favorite hyperinflated currency
$string = '123456789123456789.123456';
echo "original string: " .
$string . "<br>";
// 123456789123456789.123456
echo "float cast - " .
((float) $string) . "<br>";
// 1.23456789123E+17
echo "number_format original: " .
number_format($string, 4) . "<br>";
// 123,456,789,123,456,768.0000
echo "number_format new: " .
format::number($string, 4) . "<br>";
// 123,456,789,123,456,789.1234
echo "money_format original: " .
money_format('%n', $string) . "<br>";
// Z$ 123,456,789,123,456,784.00
echo "money_format new: " .
format::money('%n',$string) . "<br>";
// Z$ 123,456,789,123,456,789.12
精彩评论