at path:ROOT / clients / vendor / punic / punic / src / Number.php
run:R W Run
DIR
2026-04-08 19:42:30
R W Run
DIR
2026-04-08 19:53:13
R W Run
126.16 KB
2026-04-08 19:35:40
R W Run
4.19 KB
2026-04-08 19:35:40
R W Run
8.96 KB
2026-04-08 19:35:41
R W Run
24.16 KB
2026-04-08 19:35:40
R W Run
2 KB
2026-04-08 19:35:42
R W Run
3.93 KB
2026-04-08 19:35:41
R W Run
12.53 KB
2026-04-08 19:35:40
R W Run
17.07 KB
2026-04-08 19:35:39
R W Run
2.3 KB
2026-04-08 19:35:42
R W Run
6.93 KB
2026-04-08 19:35:39
R W Run
20.49 KB
2026-04-08 19:35:40
R W Run
13.16 KB
2026-04-08 19:35:39
R W Run
error_log
📄Number.php
1<?php
2
3namespace Punic;
4
5/**
6 * Numbers helpers.
7 */
8class Number
9{
10 /**
11 * Check if a variable contains a valid number for the specified locale.
12 *
13 * @param string $value The string value to check
14 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
15 *
16 * @return bool
17 */
18 public static function isNumeric($value, $locale = '')
19 {
20 return static::unformat($value, $locale) !== null;
21 }
22
23 /**
24 * Check if a variable contains a valid integer number for the specified locale.
25 *
26 * @param string $value The string value to check
27 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
28 *
29 * @return bool
30 */
31 public static function isInteger($value, $locale = '')
32 {
33 $result = false;
34 $number = static::unformat($value, $locale);
35 if (is_int($number)) {
36 $result = true;
37 } elseif (is_float($number)) {
38 if ($number === (float) round($number)) {
39 $result = true;
40 }
41 }
42
43 return $result;
44 }
45
46 /**
47 * Localize a number representation (for instance, converts 1234.5 to '1,234.5' in case of English and to '1.234,5' in case of Italian).
48 *
49 * @param int|float|string $value The string value to convert
50 * @param int|null $precision The wanted precision (well use {@link http://php.net/manual/function.round.php})
51 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
52 *
53 * @return string Returns an empty string $value is not a number, otherwise returns the localized representation of the number
54 */
55 public static function format($value, $precision = null, $locale = '')
56 {
57 $result = '';
58 $number = null;
59 if (is_numeric($value)) {
60 if (is_string($value) && $precision === null) {
61 $precision = self::getPrecision($value);
62 }
63 $number = (float) $value;
64 }
65 if ($number !== null) {
66 $data = Data::get('numbers', $locale);
67 if ($value < 0) {
68 $sign = $data['symbols']['minusSign'];
69 $number = abs($number);
70 } else {
71 $sign = '';
72 }
73 if (is_nan($number)) {
74 $result = $data['symbols']['nan'];
75 } elseif (is_infinite($number)) {
76 $result = $sign.$data['symbols']['infinity'];
77 } else {
78 $precision = is_numeric($precision) ? (int) $precision : null;
79 if ($precision !== null) {
80 $number = round($number, $precision);
81 }
82 $decimal = $data['symbols']['decimal'];
83 $groupLength = (isset($data['groupLength']) && is_numeric($data['groupLength'])) ? (int) $data['groupLength'] : 3;
84 $full = explode('.', (string) $number, 2);
85 $intPart = $full[0];
86 $floatPath = count($full) > 1 ? $full[1] : '';
87 $len = strlen($intPart);
88 if (($groupLength > 0) && ($len > $groupLength)) {
89 $groupSign = $data['symbols']['group'];
90 $preLength = 1 + (($len - 1) % 3);
91 $pre = substr($intPart, 0, $preLength);
92 $intPart = $pre.$groupSign.implode($groupSign, str_split(substr($intPart, $preLength), $groupLength));
93 }
94 $result = $sign.$intPart;
95 if ($precision === null) {
96 if ($floatPath !== '') {
97 $result .= $decimal.$floatPath;
98 }
99 } elseif ($precision > 0) {
100 $result .= $decimal.substr(str_pad($floatPath, $precision, '0', STR_PAD_RIGHT), 0, $precision);
101 }
102 }
103 }
104
105 return $result;
106 }
107
108 /**
109 * Localize a percentage (for instance, converts 12.345 to '1,234.5%' in case of English and to '1.234,5 %' in case of Danish).
110 *
111 * @param int|float|string $value The string value to convert
112 * @param int|null $precision The wanted precision (well use {@link http://php.net/manual/function.round.php})
113 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
114 *
115 * @return string Returns an empty string $value is not a number, otherwise returns the localized representation of the percentage
116 */
117 public static function formatPercent($value, $precision = null, $locale = '')
118 {
119 $result = '';
120 if (is_numeric($value)) {
121 $data = Data::get('numbers', $locale);
122
123 if ($precision === null) {
124 $precision = self::getPrecision($value);
125 }
126 $formatted = self::format(100 * abs($value), $precision, $locale);
127
128 $format = $data['percentFormats']['standard'][$value >= 0 ? 'positive' : 'negative'];
129 $sign = $data['symbols']['percentSign'];
130
131 $result = sprintf($format, $formatted, $sign);
132 }
133
134 return $result;
135 }
136
137 /**
138 * Localize a currency amount (for instance, converts 12.345 to '1,234.5%' in case of English and to '1.234,5 %' in case of Danish).
139 *
140 * @param int|float|string $value The string value to convert
141 * @param string $currencyCode The 3-letter currency code
142 * @param string $kind The currency variant, either "standard" or "accounting"
143 * @param int|null $precision The wanted precision (well use {@link http://php.net/manual/function.round.php})
144 * @param string $which The currency symbol to use, "" for default, "long" for the currency name, "narrow", "alt" for alternative, or "code" for the 3-letter currency code
145 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
146 *
147 * @return string Returns an empty string $value is not a number, otherwise returns the localized representation of the amount
148 */
149 public static function formatCurrency($value, $currencyCode, $kind = 'standard', $precision = null, $which = '', $locale = '')
150 {
151 $result = '';
152 if (is_numeric($value)) {
153 $data = Data::get('numbers', $locale);
154 $data = $data['currencyFormats'];
155
156 if ($precision === null) {
157 $currencyData = Data::getGeneric('currencyData');
158 if (isset($currencyData['fractions'][$currencyCode]['digits'])) {
159 $precision = $currencyData['fractions'][$currencyCode]['digits'];
160 } else {
161 $precision = $currencyData['fractionsDefault']['digits'];
162 }
163 }
164 $formatted = self::format(abs($value), $precision, $locale);
165
166 if (!isset($data[$kind])) {
167 throw new Exception\ValueNotInList($kind, array_keys($data));
168 }
169 $format = $data[$kind][$value >= 0 ? 'positive' : 'negative'];
170
171 $symbol = null;
172 switch ($which) {
173 case 'long':
174 $value = number_format($value, $precision, '.', '');
175 $symbol = Currency::getName($currencyCode, $value, $locale);
176 break;
177 case 'code':
178 $symbol = $currencyCode;
179 break;
180 default:
181 $symbol = Currency::getSymbol($currencyCode, $which, $locale);
182 break;
183 }
184 if (!$symbol) {
185 $symbol = $currencyCode;
186 }
187
188 if ($which === 'long') {
189 $pluralRule = 'count-'.Plural::getRuleOfType($value, Plural::RULETYPE_CARDINAL, $locale);
190 if (!isset($data['unitPattern'][$pluralRule])) {
191 $pluralRule = 'count-other';
192 }
193 $unitPattern = $data['unitPattern'][$pluralRule];
194
195 $result = sprintf($unitPattern, $formatted, $symbol);
196 } else {
197 list($before, $after) = explode('%2$s', $format);
198 if ($after &&
199 preg_match($data['currencySpacing']['afterCurrency']['currency'], $symbol) &&
200 preg_match($data['currencySpacing']['afterCurrency']['surrounding'], sprintf($after, $formatted))) {
201 $symbol .= $data['currencySpacing']['afterCurrency']['insertBetween'];
202 }
203 if ($before &&
204 preg_match($data['currencySpacing']['beforeCurrency']['currency'], $symbol) &&
205 preg_match($data['currencySpacing']['beforeCurrency']['surrounding'], sprintf($before, $formatted))) {
206 $symbol = $data['currencySpacing']['beforeCurrency']['insertBetween'].$symbol;
207 }
208
209 $result = sprintf($format, $formatted, $symbol);
210 }
211 }
212
213 return $result;
214 }
215
216 /**
217 * Convert a localized representation of a number to a number (for instance, converts the string '1,234' to 1234 in case of English and to 1.234 in case of Italian).
218 *
219 * @param string $value The string value to convert
220 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
221 *
222 * @return int|float|null Returns null if $value is not valid, the numeric value otherwise
223 */
224 public static function unformat($value, $locale = '')
225 {
226 $result = null;
227 if (is_int($value) || is_float($value)) {
228 $result = $value;
229 } elseif (is_string($value) && $value !== '') {
230 $data = Data::get('numbers', $locale);
231 $plus = $data['symbols']['plusSign'];
232 $plusQ = preg_quote($plus);
233 $minus = $data['symbols']['minusSign'];
234 $minusQ = preg_quote($minus);
235 $decimal = $data['symbols']['decimal'];
236 $decimalQ = preg_quote($decimal);
237 $group = $data['symbols']['group'];
238 $groupQ = preg_quote($group);
239 $ok = true;
240 if (preg_match('/^'."($plusQ|$minusQ)?(\\d+(?:$groupQ\\d+)*)".'$/', $value, $m)) {
241 $sign = $m[1];
242 $int = $m[2];
243 $float = null;
244 } elseif (preg_match('/^'."($plusQ|$minusQ)?(\\d+(?:$groupQ\\d+)*)$decimalQ".'$/', $value, $m)) {
245 $sign = $m[1];
246 $int = $m[2];
247 $float = '';
248 } elseif (preg_match('/^'."($plusQ|$minusQ)?(\\d+(?:$groupQ\\d+)*)$decimalQ(\\d+)".'$/', $value, $m)) {
249 $sign = $m[1];
250 $int = $m[2];
251 $float = $m[3];
252 } elseif (preg_match('/^'."($plusQ|$minusQ)?$decimalQ(\\d+)".'$/', $value, $m)) {
253 $sign = $m[1];
254 $int = '0';
255 $float = $m[2];
256 } else {
257 $ok = false;
258 $float = $int = $sign = null;
259 }
260 if ($ok) {
261 if ($sign === $minus) {
262 $sign = '-';
263 } else {
264 $sign = '';
265 }
266 $int = str_replace($group, '', $int);
267 if ($float === null) {
268 $result = (int) "$sign$int";
269 } else {
270 $result = (float) "$sign$int.$float";
271 }
272 }
273 }
274
275 return $result;
276 }
277
278 /**
279 * Spell out a number (e.g. "one hundred twenty-three" or "twenty-third") or convert to a different numbering system, e.g Roman numerals.
280 *
281 * Some types are language-dependent and reflect e.g. gender and case. Refer to the CLDR XML source for supported types.
282 *
283 * Available numbering systems are specified in the "root" locale.
284 *
285 * @param int|float|string $value The value to localize/spell out
286 * @param string $type The format type, e.g. "spellout-numbering", "spellout-numbering-year", "spellout-cardinal", "digits-ordinal", "roman-upper".
287 * @param string $locale The locale to use. If empty we'll use the default locale set in \Punic\Data
288 *
289 * @return string The spelled number
290 *
291 * @see https://www.unicode.org/repos/cldr/trunk/common/rbnf/
292 * @see https://www.unicode.org/repos/cldr/trunk/common/rbnf/root.xml
293 */
294 public static function spellOut($value, $type, $locale)
295 {
296 return self::formatRbnf($value, $type, null, $locale);
297 }
298
299 /**
300 * This method should not be called from outside this class.
301 *
302 * It is declared public for compatibility with PHP 5.3.
303 *
304 * @param int|float|string $value
305 * @param string $type
306 * @param int $base
307 * @param string $locale
308 *
309 * @internal
310 */
311 public static function formatRbnf($value, $type, $base, $locale)
312 {
313 $data = Data::get('rbnf', $locale);
314 if (!isset($data[$type])) {
315 $data += Data::get('rbnf', 'root', true);
316 }
317 if (!isset($data[$type])) {
318 throw new Exception\ValueNotInList($type, array_keys($data));
319 }
320 $data = $data[$type];
321
322 list($rule, $left, $right, $prevBase) = self::getRbnfRule($value, $data, $base);
323
324 $rule = preg_replace_callback('/([<>=])(.*?)\1\1?|\$\((.*?),(.*?)\)\$/', function ($match) use ($value, $left, $right, $type, $prevBase, $locale) {
325 if (isset($match[4])) {
326 $rule = Plural::getRuleOfType($left, $match[3] ? $match[3] : Plural::RULETYPE_CARDINAL, $locale);
327 if (preg_match('/'.$rule.'{(.*?)}/', $match[4], $match2)) {
328 return $match2[1];
329 }
330 } else {
331 $base = null;
332 if ($match[2]) {
333 if ($match[2][0] !== '%') {
334 $i = strpos($match[2], '.');
335 if ($i === false) {
336 $precision = 0;
337 } elseif ($match[2][$i + 1] === '#') {
338 $precision = null;
339 } else {
340 $precision = strspn($match[2], '0', $i + 1);
341 }
342
343 return Number::format($value, $precision, $locale);
344 }
345 $type = substr($match[2], 1);
346 }
347
348 switch ($match[1]) {
349 case '=':
350 break;
351 case '<':
352 $value = $left;
353 break;
354 case '>':
355 $value = $right;
356 if ($match[0] == '>>>') {
357 $base = $prevBase;
358 }
359 break;
360 }
361
362 return implode(' ', array_map(function ($v) use ($type, $base, $locale) {
363 return Number::formatRbnf($v, $type, $base, $locale);
364 }, (array) $value));
365 }
366 }, $rule);
367
368 return $rule;
369 }
370
371 protected static function getRbnfRule($value, $data, $base = null)
372 {
373 $left = 0;
374 $right = 0;
375 $prevBase = 0;
376 if (!is_numeric($value)) {
377 $rule = '';
378 } elseif (is_nan($value) && isset($data['NaN'])) {
379 $rule = $data['NaN']['rule'];
380 } elseif ($value < 0) {
381 $right = -$value;
382 $rule = $data['-x']['rule'];
383 } elseif (is_infinite($value) && isset($data['Inf'])) {
384 $rule = $data['Inf']['rule'];
385 } elseif (strpos($value, '.') !== false && isset($data['x.x'])) {
386 list($left, $right) = explode('.', $value);
387 $right = str_split($right);
388 if ($left == 0 && isset($data['0.x'])) {
389 $rule = $data['0.x']['rule'];
390 } else {
391 $rule = $data['x.x']['rule'];
392 }
393 } else {
394 $bases = array_keys($data['integer']);
395 if ($base) {
396 $i = array_search($base, $bases);
397 } else {
398 for ($i = count($bases) - 1; $i >= 0; --$i) {
399 $base = $bases[$i];
400 if ($base <= $value) {
401 break;
402 }
403 }
404 }
405 $prevBase = $i > 0 ? $bases[$i - 1] : null;
406
407 $r = $data['integer'][$base] + array('radix' => 10);
408 $rule = $r['rule'];
409 $radix = $r['radix'];
410
411 // Add .5 to avoid floating-point rounding error in PHP 5. $base is
412 // an integer, so adding a number < 1 will not break anything.
413 $divisor = pow($radix, floor(abs(log($base + .5, $radix))));
414
415 $right = fmod($value, $divisor);
416 $left = floor($value / $divisor);
417
418 if ($right) {
419 $rule = str_replace(array('[', ']'), '', $rule);
420 } else {
421 $rule = preg_replace('/\[.*?\]/', '', $rule);
422 }
423 }
424
425 return array($rule, $left, $right, $prevBase);
426 }
427
428 private static function getPrecision($value)
429 {
430 $precision = null;
431 if (is_string($value)) {
432 $i = strrpos($value, '.');
433 if ($i !== false) {
434 $precision = strlen($value) - $i - 1;
435 }
436 }
437
438 return $precision;
439 }
440}
441