]> git.immae.eu Git - github/wallabag/wallabag.git/blob - vendor/symfony/intl/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php
gitignore vendor
[github/wallabag/wallabag.git] / vendor / symfony / intl / Symfony / Component / Intl / NumberFormatter / NumberFormatter.php
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Intl\NumberFormatter;
13
14 use Symfony\Component\Intl\Exception\NotImplementedException;
15 use Symfony\Component\Intl\Exception\MethodNotImplementedException;
16 use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException;
17 use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException;
18 use Symfony\Component\Intl\Globals\IntlGlobals;
19 use Symfony\Component\Intl\Intl;
20 use Symfony\Component\Intl\Locale\Locale;
21
22 /**
23 * Replacement for PHP's native {@link \NumberFormatter} class.
24 *
25 * The only methods currently supported in this class are:
26 *
27 * - {@link __construct}
28 * - {@link create}
29 * - {@link formatCurrency}
30 * - {@link format}
31 * - {@link getAttribute}
32 * - {@link getErrorCode}
33 * - {@link getErrorMessage}
34 * - {@link getLocale}
35 * - {@link parse}
36 * - {@link setAttribute}
37 *
38 * @author Eriksen Costa <eriksen.costa@infranology.com.br>
39 * @author Bernhard Schussek <bschussek@gmail.com>
40 */
41 class NumberFormatter
42 {
43 /* Format style constants */
44 const PATTERN_DECIMAL = 0;
45 const DECIMAL = 1;
46 const CURRENCY = 2;
47 const PERCENT = 3;
48 const SCIENTIFIC = 4;
49 const SPELLOUT = 5;
50 const ORDINAL = 6;
51 const DURATION = 7;
52 const PATTERN_RULEBASED = 9;
53 const IGNORE = 0;
54 const DEFAULT_STYLE = 1;
55
56 /* Format type constants */
57 const TYPE_DEFAULT = 0;
58 const TYPE_INT32 = 1;
59 const TYPE_INT64 = 2;
60 const TYPE_DOUBLE = 3;
61 const TYPE_CURRENCY = 4;
62
63 /* Numeric attribute constants */
64 const PARSE_INT_ONLY = 0;
65 const GROUPING_USED = 1;
66 const DECIMAL_ALWAYS_SHOWN = 2;
67 const MAX_INTEGER_DIGITS = 3;
68 const MIN_INTEGER_DIGITS = 4;
69 const INTEGER_DIGITS = 5;
70 const MAX_FRACTION_DIGITS = 6;
71 const MIN_FRACTION_DIGITS = 7;
72 const FRACTION_DIGITS = 8;
73 const MULTIPLIER = 9;
74 const GROUPING_SIZE = 10;
75 const ROUNDING_MODE = 11;
76 const ROUNDING_INCREMENT = 12;
77 const FORMAT_WIDTH = 13;
78 const PADDING_POSITION = 14;
79 const SECONDARY_GROUPING_SIZE = 15;
80 const SIGNIFICANT_DIGITS_USED = 16;
81 const MIN_SIGNIFICANT_DIGITS = 17;
82 const MAX_SIGNIFICANT_DIGITS = 18;
83 const LENIENT_PARSE = 19;
84
85 /* Text attribute constants */
86 const POSITIVE_PREFIX = 0;
87 const POSITIVE_SUFFIX = 1;
88 const NEGATIVE_PREFIX = 2;
89 const NEGATIVE_SUFFIX = 3;
90 const PADDING_CHARACTER = 4;
91 const CURRENCY_CODE = 5;
92 const DEFAULT_RULESET = 6;
93 const PUBLIC_RULESETS = 7;
94
95 /* Format symbol constants */
96 const DECIMAL_SEPARATOR_SYMBOL = 0;
97 const GROUPING_SEPARATOR_SYMBOL = 1;
98 const PATTERN_SEPARATOR_SYMBOL = 2;
99 const PERCENT_SYMBOL = 3;
100 const ZERO_DIGIT_SYMBOL = 4;
101 const DIGIT_SYMBOL = 5;
102 const MINUS_SIGN_SYMBOL = 6;
103 const PLUS_SIGN_SYMBOL = 7;
104 const CURRENCY_SYMBOL = 8;
105 const INTL_CURRENCY_SYMBOL = 9;
106 const MONETARY_SEPARATOR_SYMBOL = 10;
107 const EXPONENTIAL_SYMBOL = 11;
108 const PERMILL_SYMBOL = 12;
109 const PAD_ESCAPE_SYMBOL = 13;
110 const INFINITY_SYMBOL = 14;
111 const NAN_SYMBOL = 15;
112 const SIGNIFICANT_DIGIT_SYMBOL = 16;
113 const MONETARY_GROUPING_SEPARATOR_SYMBOL = 17;
114
115 /* Rounding mode values used by NumberFormatter::setAttribute() with NumberFormatter::ROUNDING_MODE attribute */
116 const ROUND_CEILING = 0;
117 const ROUND_FLOOR = 1;
118 const ROUND_DOWN = 2;
119 const ROUND_UP = 3;
120 const ROUND_HALFEVEN = 4;
121 const ROUND_HALFDOWN = 5;
122 const ROUND_HALFUP = 6;
123
124 /* Pad position values used by NumberFormatter::setAttribute() with NumberFormatter::PADDING_POSITION attribute */
125 const PAD_BEFORE_PREFIX = 0;
126 const PAD_AFTER_PREFIX = 1;
127 const PAD_BEFORE_SUFFIX = 2;
128 const PAD_AFTER_SUFFIX = 3;
129
130 /**
131 * The error code from the last operation
132 *
133 * @var integer
134 */
135 protected $errorCode = IntlGlobals::U_ZERO_ERROR;
136
137 /**
138 * The error message from the last operation
139 *
140 * @var string
141 */
142 protected $errorMessage = 'U_ZERO_ERROR';
143
144 /**
145 * @var int
146 */
147 private $style;
148
149 /**
150 * Default values for the en locale
151 *
152 * @var array
153 */
154 private $attributes = array(
155 self::FRACTION_DIGITS => 0,
156 self::GROUPING_USED => 1,
157 self::ROUNDING_MODE => self::ROUND_HALFEVEN
158 );
159
160 /**
161 * Holds the initialized attributes code
162 *
163 * @var array
164 */
165 private $initializedAttributes = array();
166
167 /**
168 * The supported styles to the constructor $styles argument
169 *
170 * @var array
171 */
172 private static $supportedStyles = array(
173 'CURRENCY' => self::CURRENCY,
174 'DECIMAL' => self::DECIMAL
175 );
176
177 /**
178 * Supported attributes to the setAttribute() $attr argument
179 *
180 * @var array
181 */
182 private static $supportedAttributes = array(
183 'FRACTION_DIGITS' => self::FRACTION_DIGITS,
184 'GROUPING_USED' => self::GROUPING_USED,
185 'ROUNDING_MODE' => self::ROUNDING_MODE
186 );
187
188 /**
189 * The available rounding modes for setAttribute() usage with
190 * NumberFormatter::ROUNDING_MODE. NumberFormatter::ROUND_DOWN
191 * and NumberFormatter::ROUND_UP does not have a PHP only equivalent
192 *
193 * @var array
194 */
195 private static $roundingModes = array(
196 'ROUND_HALFEVEN' => self::ROUND_HALFEVEN,
197 'ROUND_HALFDOWN' => self::ROUND_HALFDOWN,
198 'ROUND_HALFUP' => self::ROUND_HALFUP
199 );
200
201 /**
202 * The mapping between NumberFormatter rounding modes to the available
203 * modes in PHP's round() function.
204 *
205 * @see http://www.php.net/manual/en/function.round.php
206 *
207 * @var array
208 */
209 private static $phpRoundingMap = array(
210 self::ROUND_HALFDOWN => \PHP_ROUND_HALF_DOWN,
211 self::ROUND_HALFEVEN => \PHP_ROUND_HALF_EVEN,
212 self::ROUND_HALFUP => \PHP_ROUND_HALF_UP
213 );
214
215 /**
216 * The maximum values of the integer type in 32 bit platforms.
217 *
218 * @var array
219 */
220 private static $int32Range = array(
221 'positive' => 2147483647,
222 'negative' => -2147483648
223 );
224
225 /**
226 * The maximum values of the integer type in 64 bit platforms.
227 *
228 * @var array
229 */
230 private static $int64Range = array(
231 'positive' => 9223372036854775807,
232 'negative' => -9223372036854775808
233 );
234
235 /**
236 * Constructor.
237 *
238 * @param string $locale The locale code. The only currently supported locale is "en".
239 * @param int $style Style of the formatting, one of the format style constants.
240 * The only supported styles are NumberFormatter::DECIMAL
241 * and NumberFormatter::CURRENCY.
242 * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or
243 * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax
244 * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation
245 *
246 * @see http://www.php.net/manual/en/numberformatter.create.php
247 * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details
248 * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details
249 *
250 * @throws MethodArgumentValueNotImplementedException When $locale different than "en" is passed
251 * @throws MethodArgumentValueNotImplementedException When the $style is not supported
252 * @throws MethodArgumentNotImplementedException When the pattern value is different than null
253 */
254 public function __construct($locale = 'en', $style = null, $pattern = null)
255 {
256 if ('en' != $locale) {
257 throw new MethodArgumentValueNotImplementedException(__METHOD__, 'locale', $locale, 'Only the locale "en" is supported');
258 }
259
260 if (!in_array($style, self::$supportedStyles)) {
261 $message = sprintf('The available styles are: %s.', implode(', ', array_keys(self::$supportedStyles)));
262 throw new MethodArgumentValueNotImplementedException(__METHOD__, 'style', $style, $message);
263 }
264
265 if (null !== $pattern) {
266 throw new MethodArgumentNotImplementedException(__METHOD__, 'pattern');
267 }
268
269 $this->style = $style;
270 }
271
272 /**
273 * Static constructor.
274 *
275 * @param string $locale The locale code. The only supported locale is "en".
276 * @param int $style Style of the formatting, one of the format style constants.
277 * The only currently supported styles are NumberFormatter::DECIMAL
278 * and NumberFormatter::CURRENCY.
279 * @param string $pattern Not supported. A pattern string in case $style is NumberFormat::PATTERN_DECIMAL or
280 * NumberFormat::PATTERN_RULEBASED. It must conform to the syntax
281 * described in the ICU DecimalFormat or ICU RuleBasedNumberFormat documentation
282 *
283 * @return NumberFormatter
284 *
285 * @see http://www.php.net/manual/en/numberformatter.create.php
286 * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details
287 * @see http://www.icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html#_details
288 *
289 * @throws MethodArgumentValueNotImplementedException When $locale different than "en" is passed
290 * @throws MethodArgumentValueNotImplementedException When the $style is not supported
291 * @throws MethodArgumentNotImplementedException When the pattern value is different than null
292 */
293 public static function create($locale = 'en', $style = null, $pattern = null)
294 {
295 return new self($locale, $style, $pattern);
296 }
297
298 /**
299 * Format a currency value
300 *
301 * @param float $value The numeric currency value
302 * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use
303 *
304 * @return string The formatted currency value
305 *
306 * @see http://www.php.net/manual/en/numberformatter.formatcurrency.php
307 * @see http://www.iso.org/iso/support/faqs/faqs_widely_used_standards/widely_used_standards_other/currency_codes/currency_codes_list-1.htm
308 */
309 public function formatCurrency($value, $currency)
310 {
311 if ($this->style == self::DECIMAL) {
312 return $this->format($value);
313 }
314
315 $symbol = Intl::getCurrencyBundle()->getCurrencySymbol($currency, 'en');
316 $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency);
317
318 $value = $this->roundCurrency($value, $currency);
319
320 $negative = false;
321 if (0 > $value) {
322 $negative = true;
323 $value *= -1;
324 }
325
326 $value = $this->formatNumber($value, $fractionDigits);
327
328 $ret = $symbol.$value;
329
330 return $negative ? '('.$ret.')' : $ret;
331 }
332
333 /**
334 * Format a number
335 *
336 * @param number $value The value to format
337 * @param int $type Type of the formatting, one of the format type constants.
338 * Only type NumberFormatter::TYPE_DEFAULT is currently supported.
339 *
340 * @return Boolean|string The formatted value or false on error
341 *
342 * @see http://www.php.net/manual/en/numberformatter.format.php
343 *
344 * @throws NotImplementedException If the method is called with the class $style 'CURRENCY'
345 * @throws MethodArgumentValueNotImplementedException If the $type is different than TYPE_DEFAULT
346 */
347 public function format($value, $type = self::TYPE_DEFAULT)
348 {
349 // The original NumberFormatter does not support this format type
350 if ($type == self::TYPE_CURRENCY) {
351 trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
352
353 return false;
354 }
355
356 if ($this->style == self::CURRENCY) {
357 throw new NotImplementedException(sprintf(
358 '%s() method does not support the formatting of currencies (instance with CURRENCY style). %s',
359 __METHOD__, NotImplementedException::INTL_INSTALL_MESSAGE
360 ));
361 }
362
363 // Only the default type is supported.
364 if ($type != self::TYPE_DEFAULT) {
365 throw new MethodArgumentValueNotImplementedException(__METHOD__, 'type', $type, 'Only TYPE_DEFAULT is supported');
366 }
367
368 $fractionDigits = $this->getAttribute(self::FRACTION_DIGITS);
369
370 $value = $this->round($value, $fractionDigits);
371 $value = $this->formatNumber($value, $fractionDigits);
372
373 // behave like the intl extension
374 $this->resetError();
375
376 return $value;
377 }
378
379 /**
380 * Returns an attribute value
381 *
382 * @param int $attr An attribute specifier, one of the numeric attribute constants
383 *
384 * @return Boolean|int The attribute value on success or false on error
385 *
386 * @see http://www.php.net/manual/en/numberformatter.getattribute.php
387 */
388 public function getAttribute($attr)
389 {
390 return isset($this->attributes[$attr]) ? $this->attributes[$attr] : null;
391 }
392
393 /**
394 * Returns formatter's last error code. Always returns the U_ZERO_ERROR class constant value
395 *
396 * @return int The error code from last formatter call
397 *
398 * @see http://www.php.net/manual/en/numberformatter.geterrorcode.php
399 */
400 public function getErrorCode()
401 {
402 return $this->errorCode;
403 }
404
405 /**
406 * Returns formatter's last error message. Always returns the U_ZERO_ERROR_MESSAGE class constant value
407 *
408 * @return string The error message from last formatter call
409 *
410 * @see http://www.php.net/manual/en/numberformatter.geterrormessage.php
411 */
412 public function getErrorMessage()
413 {
414 return $this->errorMessage;
415 }
416
417 /**
418 * Returns the formatter's locale
419 *
420 * The parameter $type is currently ignored.
421 *
422 * @param int $type Not supported. The locale name type to return (Locale::VALID_LOCALE or Locale::ACTUAL_LOCALE)
423 *
424 * @return string The locale used to create the formatter. Currently always
425 * returns "en".
426 *
427 * @see http://www.php.net/manual/en/numberformatter.getlocale.php
428 */
429 public function getLocale($type = Locale::ACTUAL_LOCALE)
430 {
431 return 'en';
432 }
433
434 /**
435 * Not supported. Returns the formatter's pattern
436 *
437 * @return Boolean|string The pattern string used by the formatter or false on error
438 *
439 * @see http://www.php.net/manual/en/numberformatter.getpattern.php
440 *
441 * @throws MethodNotImplementedException
442 */
443 public function getPattern()
444 {
445 throw new MethodNotImplementedException(__METHOD__);
446 }
447
448 /**
449 * Not supported. Returns a formatter symbol value
450 *
451 * @param int $attr A symbol specifier, one of the format symbol constants
452 *
453 * @return Boolean|string The symbol value or false on error
454 *
455 * @see http://www.php.net/manual/en/numberformatter.getsymbol.php
456 *
457 * @throws MethodNotImplementedException
458 */
459 public function getSymbol($attr)
460 {
461 throw new MethodNotImplementedException(__METHOD__);
462 }
463
464 /**
465 * Not supported. Returns a formatter text attribute value
466 *
467 * @param int $attr An attribute specifier, one of the text attribute constants
468 *
469 * @return Boolean|string The attribute value or false on error
470 *
471 * @see http://www.php.net/manual/en/numberformatter.gettextattribute.php
472 *
473 * @throws MethodNotImplementedException
474 */
475 public function getTextAttribute($attr)
476 {
477 throw new MethodNotImplementedException(__METHOD__);
478 }
479
480 /**
481 * Not supported. Parse a currency number
482 *
483 * @param string $value The value to parse
484 * @param string $currency Parameter to receive the currency name (reference)
485 * @param int $position Offset to begin the parsing on return this value will hold the offset at which the parsing ended
486 *
487 * @return Boolean|string The parsed numeric value of false on error
488 *
489 * @see http://www.php.net/manual/en/numberformatter.parsecurrency.php
490 *
491 * @throws MethodNotImplementedException
492 */
493 public function parseCurrency($value, &$currency, &$position = null)
494 {
495 throw new MethodNotImplementedException(__METHOD__);
496 }
497
498 /**
499 * Parse a number
500 *
501 * @param string $value The value to parse
502 * @param int $type Type of the formatting, one of the format type constants.
503 * The only currently supported types are NumberFormatter::TYPE_DOUBLE,
504 * NumberFormatter::TYPE_INT32 and NumberFormatter::TYPE_INT64.
505 * @param int $position Not supported. Offset to begin the parsing on return this value will hold the offset at which the parsing ended
506 *
507 * @return Boolean|string The parsed value of false on error
508 *
509 * @see http://www.php.net/manual/en/numberformatter.parse.php
510 *
511 * @throws MethodArgumentNotImplementedException When $position different than null, behavior not implemented
512 */
513 public function parse($value, $type = self::TYPE_DOUBLE, &$position = null)
514 {
515 if ($type == self::TYPE_DEFAULT || $type == self::TYPE_CURRENCY) {
516 trigger_error(__METHOD__.'(): Unsupported format type '.$type, \E_USER_WARNING);
517
518 return false;
519 }
520
521 // We don't calculate the position when parsing the value
522 if (null !== $position) {
523 throw new MethodArgumentNotImplementedException(__METHOD__, 'position');
524 }
525
526 preg_match('/^([^0-9\-]{0,})(.*)/', $value, $matches);
527
528 // Any string before the numeric value causes error in the parsing
529 if (isset($matches[1]) && !empty($matches[1])) {
530 IntlGlobals::setError(IntlGlobals::U_PARSE_ERROR, 'Number parsing failed');
531 $this->errorCode = IntlGlobals::getErrorCode();
532 $this->errorMessage = IntlGlobals::getErrorMessage();
533
534 return false;
535 }
536
537 // Remove everything that is not number or dot (.)
538 $value = preg_replace('/[^0-9\.\-]/', '', $value);
539 $value = $this->convertValueDataType($value, $type);
540
541 // behave like the intl extension
542 $this->resetError();
543
544 return $value;
545 }
546
547 /**
548 * Set an attribute
549 *
550 * @param int $attr An attribute specifier, one of the numeric attribute constants.
551 * The only currently supported attributes are NumberFormatter::FRACTION_DIGITS,
552 * NumberFormatter::GROUPING_USED and NumberFormatter::ROUNDING_MODE.
553 * @param int $value The attribute value. The only currently supported rounding modes are
554 * NumberFormatter::ROUND_HALFEVEN, NumberFormatter::ROUND_HALFDOWN and
555 * NumberFormatter::ROUND_HALFUP.
556 *
557 * @return Boolean true on success or false on failure
558 *
559 * @see http://www.php.net/manual/en/numberformatter.setattribute.php
560 *
561 * @throws MethodArgumentValueNotImplementedException When the $attr is not supported
562 * @throws MethodArgumentValueNotImplementedException When the $value is not supported
563 */
564 public function setAttribute($attr, $value)
565 {
566 if (!in_array($attr, self::$supportedAttributes)) {
567 $message = sprintf(
568 'The available attributes are: %s',
569 implode(', ', array_keys(self::$supportedAttributes))
570 );
571
572 throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message);
573 }
574
575 if (self::$supportedAttributes['ROUNDING_MODE'] == $attr && $this->isInvalidRoundingMode($value)) {
576 $message = sprintf(
577 'The supported values for ROUNDING_MODE are: %s',
578 implode(', ', array_keys(self::$roundingModes))
579 );
580
581 throw new MethodArgumentValueNotImplementedException(__METHOD__, 'attr', $value, $message);
582 }
583
584 if (self::$supportedAttributes['GROUPING_USED'] == $attr) {
585 $value = $this->normalizeGroupingUsedValue($value);
586 }
587
588 if (self::$supportedAttributes['FRACTION_DIGITS'] == $attr) {
589 $value = $this->normalizeFractionDigitsValue($value);
590 }
591
592 $this->attributes[$attr] = $value;
593 $this->initializedAttributes[$attr] = true;
594
595 return true;
596 }
597
598 /**
599 * Not supported. Set the formatter's pattern
600 *
601 * @param string $pattern A pattern string in conformance with the ICU DecimalFormat documentation
602 *
603 * @return Boolean true on success or false on failure
604 *
605 * @see http://www.php.net/manual/en/numberformatter.setpattern.php
606 * @see http://www.icu-project.org/apiref/icu4c/classDecimalFormat.html#_details
607 *
608 * @throws MethodNotImplementedException
609 */
610 public function setPattern($pattern)
611 {
612 throw new MethodNotImplementedException(__METHOD__);
613 }
614
615 /**
616 * Not supported. Set the formatter's symbol
617 *
618 * @param int $attr A symbol specifier, one of the format symbol constants
619 * @param string $value The value for the symbol
620 *
621 * @return Boolean true on success or false on failure
622 *
623 * @see http://www.php.net/manual/en/numberformatter.setsymbol.php
624 *
625 * @throws MethodNotImplementedException
626 */
627 public function setSymbol($attr, $value)
628 {
629 throw new MethodNotImplementedException(__METHOD__);
630 }
631
632 /**
633 * Not supported. Set a text attribute
634 *
635 * @param int $attr An attribute specifier, one of the text attribute constants
636 * @param int $value The attribute value
637 *
638 * @return Boolean true on success or false on failure
639 *
640 * @see http://www.php.net/manual/en/numberformatter.settextattribute.php
641 *
642 * @throws MethodNotImplementedException
643 */
644 public function setTextAttribute($attr, $value)
645 {
646 throw new MethodNotImplementedException(__METHOD__);
647 }
648
649 /**
650 * Set the error to the default U_ZERO_ERROR
651 */
652 protected function resetError()
653 {
654 IntlGlobals::setError(IntlGlobals::U_ZERO_ERROR);
655 $this->errorCode = IntlGlobals::getErrorCode();
656 $this->errorMessage = IntlGlobals::getErrorMessage();
657 }
658
659 /**
660 * Rounds a currency value, applying increment rounding if applicable
661 *
662 * When a currency have a rounding increment, an extra round is made after the first one. The rounding factor is
663 * determined in the ICU data and is explained as of:
664 *
665 * "the rounding increment is given in units of 10^(-fraction_digits)"
666 *
667 * The only actual rounding data as of this writing, is CHF.
668 *
669 * @param float $value The numeric currency value
670 * @param string $currency The 3-letter ISO 4217 currency code indicating the currency to use
671 *
672 * @return string The rounded numeric currency value
673 *
674 * @see http://en.wikipedia.org/wiki/Swedish_rounding
675 * @see http://www.docjar.com/html/api/com/ibm/icu/util/Currency.java.html#1007
676 */
677 private function roundCurrency($value, $currency)
678 {
679 $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency);
680 $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement($currency);
681
682 // Round with the formatter rounding mode
683 $value = $this->round($value, $fractionDigits);
684
685 // Swiss rounding
686 if (0 < $roundingIncrement && 0 < $fractionDigits) {
687 $roundingFactor = $roundingIncrement / pow(10, $fractionDigits);
688 $value = round($value / $roundingFactor) * $roundingFactor;
689 }
690
691 return $value;
692 }
693
694 /**
695 * Rounds a value.
696 *
697 * @param integer|float $value The value to round
698 * @param int $precision The number of decimal digits to round to
699 *
700 * @return integer|float The rounded value
701 */
702 private function round($value, $precision)
703 {
704 $precision = $this->getUnitializedPrecision($value, $precision);
705
706 $roundingMode = self::$phpRoundingMap[$this->getAttribute(self::ROUNDING_MODE)];
707 $value = round($value, $precision, $roundingMode);
708
709 return $value;
710 }
711
712 /**
713 * Formats a number.
714 *
715 * @param integer|float $value The numeric value to format
716 * @param int $precision The number of decimal digits to use
717 *
718 * @return string The formatted number
719 */
720 private function formatNumber($value, $precision)
721 {
722 $precision = $this->getUnitializedPrecision($value, $precision);
723
724 return number_format($value, $precision, '.', $this->getAttribute(self::GROUPING_USED) ? ',' : '');
725 }
726
727 /**
728 * Returns the precision value if the DECIMAL style is being used and the FRACTION_DIGITS attribute is unitialized.
729 *
730 * @param integer|float $value The value to get the precision from if the FRACTION_DIGITS attribute is unitialized
731 * @param int $precision The precision value to returns if the FRACTION_DIGITS attribute is initialized
732 *
733 * @return int The precision value
734 */
735 private function getUnitializedPrecision($value, $precision)
736 {
737 if ($this->style == self::CURRENCY) {
738 return $precision;
739 }
740
741 if (!$this->isInitializedAttribute(self::FRACTION_DIGITS)) {
742 preg_match('/.*\.(.*)/', (string) $value, $digits);
743 if (isset($digits[1])) {
744 $precision = strlen($digits[1]);
745 }
746 }
747
748 return $precision;
749 }
750
751 /**
752 * Check if the attribute is initialized (value set by client code).
753 *
754 * @param string $attr The attribute name
755 *
756 * @return Boolean true if the value was set by client, false otherwise
757 */
758 private function isInitializedAttribute($attr)
759 {
760 return isset($this->initializedAttributes[$attr]);
761 }
762
763 /**
764 * Returns the numeric value using the $type to convert to the right data type.
765 *
766 * @param mixed $value The value to be converted
767 * @param int $type The type to convert. Can be TYPE_DOUBLE (float) or TYPE_INT32 (int)
768 *
769 * @return integer|float The converted value
770 */
771 private function convertValueDataType($value, $type)
772 {
773 if ($type == self::TYPE_DOUBLE) {
774 $value = (float) $value;
775 } elseif ($type == self::TYPE_INT32) {
776 $value = $this->getInt32Value($value);
777 } elseif ($type == self::TYPE_INT64) {
778 $value = $this->getInt64Value($value);
779 }
780
781 return $value;
782 }
783
784 /**
785 * Convert the value data type to int or returns false if the value is out of the integer value range.
786 *
787 * @param mixed $value The value to be converted
788 *
789 * @return int The converted value
790 */
791 private function getInt32Value($value)
792 {
793 if ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) {
794 return false;
795 }
796
797 return (int) $value;
798 }
799
800 /**
801 * Convert the value data type to int or returns false if the value is out of the integer value range.
802 *
803 * @param mixed $value The value to be converted
804 *
805 * @return int|float The converted value
806 *
807 * @see https://bugs.php.net/bug.php?id=59597 Bug #59597
808 */
809 private function getInt64Value($value)
810 {
811 if ($value > self::$int64Range['positive'] || $value < self::$int64Range['negative']) {
812 return false;
813 }
814
815 if (PHP_INT_SIZE !== 8 && ($value > self::$int32Range['positive'] || $value <= self::$int32Range['negative'])) {
816 // Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
817 // The negative PHP_INT_MAX was being converted to float
818 if (
819 $value == self::$int32Range['negative'] &&
820 (
821 (version_compare(PHP_VERSION, '5.4.0', '<') && version_compare(PHP_VERSION, '5.3.14', '>=')) ||
822 version_compare(PHP_VERSION, '5.4.4', '>=')
823 )
824 ) {
825 return (int) $value;
826 }
827
828 return (float) $value;
829 }
830
831 if (PHP_INT_SIZE === 8) {
832 // Bug #59597 was fixed on PHP 5.3.14 and 5.4.4
833 // A 32 bit integer was being generated instead of a 64 bit integer
834 if (
835 ($value > self::$int32Range['positive'] || $value < self::$int32Range['negative']) &&
836 (
837 (version_compare(PHP_VERSION, '5.3.14', '<')) ||
838 (version_compare(PHP_VERSION, '5.4.0', '>=') && version_compare(PHP_VERSION, '5.4.4', '<'))
839 )
840 ) {
841 $value = (-2147483648 - ($value % -2147483648)) * ($value / abs($value));
842 }
843 }
844
845 return (int) $value;
846 }
847
848 /**
849 * Check if the rounding mode is invalid.
850 *
851 * @param int $value The rounding mode value to check
852 *
853 * @return Boolean true if the rounding mode is invalid, false otherwise
854 */
855 private function isInvalidRoundingMode($value)
856 {
857 if (in_array($value, self::$roundingModes, true)) {
858 return false;
859 }
860
861 return true;
862 }
863
864 /**
865 * Returns the normalized value for the GROUPING_USED attribute. Any value that can be converted to int will be
866 * cast to Boolean and then to int again. This way, negative values are converted to 1 and string values to 0.
867 *
868 * @param mixed $value The value to be normalized
869 *
870 * @return int The normalized value for the attribute (0 or 1)
871 */
872 private function normalizeGroupingUsedValue($value)
873 {
874 return (int) (Boolean) (int) $value;
875 }
876
877 /**
878 * Returns the normalized value for the FRACTION_DIGITS attribute. The value is converted to int and if negative,
879 * the returned value will be 0.
880 *
881 * @param mixed $value The value to be normalized
882 *
883 * @return int The normalized value for the attribute
884 */
885 private function normalizeFractionDigitsValue($value)
886 {
887 $value = (int) $value;
888
889 return (0 > $value) ? 0 : $value;
890 }
891 }