2

是否有可以使用字符串而不是浮点数作为参数的money_format的任意精度替代方案?

并不是说我打算对数万亿个货币单位进行计算,而是在经历了在不滥用浮点数的情况下正确处理货币算术的麻烦之后,如果有一个不会在大约 15 位数字后产生随机数的函数,那就太好了,即使用户决定给它无意义的数据。或者,嘿,也许有人想用津巴布韦元买两根口香糖?

我对使用正则表达式犹豫不决,因为我希望利用money_format 的本地化。

编辑 - 找到一个可行的解决方案;见下文

4

2 回答 2

0

尝试NumberFormatter

于 2010-09-27T06:03:49.353 回答
0

由 PHP 网站herehere上的评论者提交的函数拼凑而成。编辑为使用任意精度参数。

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;
    }
}

测试:

 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
于 2010-09-27T08:51:52.893 回答