代码之家  ›  专栏  ›  技术社区  ›  Greg

任意精度数字格式/货币格式?

  •  2
  • Greg  · 技术社区  · 15 年前

    有没有一种任意精度的替代方法 money_format 是否可以将字符串而不是浮点作为参数?

    并不是说我打算用数万亿的货币单位来计算,而是在经历了正确处理货币算术而不滥用浮点数的麻烦之后,如果有一个函数在大约15位之后不会吐出随机数就好了,即使用户决定给它一些无意义的数据。或者,嘿,也许有人想买 Zimbabwe dollars ?

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

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

    2 回复  |  直到 15 年前
        1
  •  0
  •   bcosca    15 年前

    试试这个 NumberFormatter

        2
  •  0
  •   Greg    15 年前

    here here . 编辑为使用任意精度参数。

    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