2

Есть ли альтернатива произвольной точности для money_format, которая может принимать строку вместо float в качестве параметра?Произвольное форматирование числа чисел/money_format?

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

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

изменить - найдено работоспособное решение; смотри ниже

+0

круглые() или number_format() не достаточно хорошо? просто нужно просто заставить строку плавать. – Cesar

+0

@Cesar - Приведение его явно не делает ничего для ошибок округления. Попробуйте отличить большое количество строк (~ 20 цифр или около того), чтобы плавать, и вы получите что-то вроде 3.17817313888E + 20. Подайте его в функцию форматирования, которая принимает float как money_format или number_format, и вы получите случайный, неправильный мусор после стольких цифр. Я думаю, что это просто при использовании поплавков. У вас есть пятнадцать цифр точности. Я знаю, насколько это анально-ретентивно, но все же - кажется, что такая функция должна принимать строковый параметр. – Greg

ответ

0

слепленный из комментатора представил функций на сайте here и here 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

попробовать NumberFormatter класс

+0

Боюсь, у меня нет этого расширения PECL, чтобы проверить его, но похоже, что это поплавок в качестве параметра? Может, я не понимаю? Примеры имеют тонну цифр, но большинство из них незначительны и просто округлены. – Greg