Произвольная точность форматирования чисел / money_format? - PullRequest
2 голосов
/ 27 сентября 2010

Есть ли альтернатива money_format произвольной точности, которая могла бы принимать строку вместо плавающей запятой в качестве параметра?

Дело не в том, что я планирую проводить расчеты на триллионах денежных единиц, но после того, как у меня возникнут проблемы с правильной обработкой денежной арифметики без злоупотребления числами, было бы неплохо иметь функцию, которая не выдает случайные числа после 15 цифр, даже если пользователи решат дать ерунду данным. Или, может быть, кто-то хочет купить две жевательные резинки в зимбабвийских долларах ?

Я не решаюсь использовать регулярные выражения, потому что я надеялся использовать локализацию money_format.

правка - найдено работоспособное решение; см. ниже

Ответы [ 2 ]

0 голосов
/ 27 сентября 2010

Собраны из представленных комментарием функций на сайте PHP здесь и здесь Отредактировано для работы с параметрами произвольной точности.

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
0 голосов
/ 27 сентября 2010

попробуйте NumberFormatter класс

...