-
-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathNumericHelper.php
More file actions
201 lines (175 loc) · 6.03 KB
/
NumericHelper.php
File metadata and controls
201 lines (175 loc) · 6.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
<?php
declare(strict_types=1);
namespace Yiisoft\Strings;
use InvalidArgumentException;
use Stringable;
use function filter_var;
use function fmod;
use function gettype;
use function in_array;
use function is_bool;
use function is_numeric;
use function is_scalar;
use function preg_match;
use function preg_replace;
use function str_replace;
use function substr;
/**
* Provides static methods to work with numeric strings.
*/
final class NumericHelper
{
/**
* @psalm-var array<int, array<string, int>>
*/
private const FILESYSTEM_SIZE_POSTFIXES = [
3 => [
'KiB' => 1024,
'MiB' => 1048576,
'GiB' => 1073741824,
'TiB' => 1099511627776,
'PiB' => 1125899906842624,
],
2 => [
'kB' => 1000,
'MB' => 1000000,
'GB' => 1000000000,
'TB' => 1000000000000,
'PB' => 1000000000000000,
],
1 => [
'k' => 1024,
'K' => 1024,
'm' => 1048576,
'M' => 1048576,
'g' => 1073741824,
'G' => 1073741824,
't' => 1099511627776,
'T' => 1099511627776,
'p' => 1125899906842624,
'P' => 1125899906842624,
],
];
/**
* Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd etc.
*
* @param float|int|string $value The number to get its ordinal value.
*/
public static function toOrdinal(mixed $value): string
{
if (!is_numeric($value)) {
$type = gettype($value);
throw new InvalidArgumentException("Value must be numeric. $type given.");
}
if (fmod((float)$value, 1) !== 0.00) {
return (string)$value;
}
if (in_array($value % 100, [11, 12, 13], true)) {
return $value . 'th';
}
return match ($value % 10) {
1 => $value . 'st',
2 => $value . 'nd',
3 => $value . 'rd',
default => $value . 'th',
};
}
/**
* Returns string representation of a number value without thousands separators and with dot as decimal separator.
*
* @param bool|float|int|string|Stringable $value String in `string` or `Stringable` must be valid UTF-8 string.
*
* @throws InvalidArgumentException if value is not scalar.
*/
public static function normalize(mixed $value): string
{
/** @psalm-suppress DocblockTypeContradiction */
if (!is_scalar($value) && !$value instanceof Stringable) {
$type = gettype($value);
throw new InvalidArgumentException("Value must be scalar. $type given.");
}
if (is_bool($value)) {
return $value ? '1' : '0';
}
$value = str_replace([' ', ','], ['', '.'], (string) $value);
/**
* @var string We assume that `$value` is valid UTF-8 string, so `preg_replace()` never returns `false`.
*/
return preg_replace('/\.(?=.*\.)/', '', $value);
}
/**
* Checks whether the given string is an integer number.
*
* Require Filter PHP extension ({@see https://www.php.net/manual/intro.filter.php}).
*/
public static function isInteger(mixed $value): bool
{
return filter_var($value, FILTER_VALIDATE_INT) !== false;
}
/**
* Converts human-readable size to bytes.
*
* @param string $string Human readable size. Examples: `1024`, `1kB`, `1.5M`, `1GiB`. Full
* list of supported postfixes in {@see FILESYSTEM_SIZE_POSTFIXES}.
* Note: This parameter must be less than `8192P` on 64-bit systems and `2G` on 32-bit systems.
*
* @throws InvalidArgumentException when the string is invalid.
*
* @return int The number of bytes equivalent to the specified string.
*
* @see https://www.gnu.org/software/coreutils/manual/html_node/Block-size.html
*/
public static function convertHumanReadableSizeToBytes(string $string): int
{
if (is_numeric($string)) {
return (int) $string;
}
foreach (self::FILESYSTEM_SIZE_POSTFIXES as $postfixLength => $postfixes) {
$postfix = substr($string, -$postfixLength);
if ($postfix === '' || preg_match('/\\d/', $postfix) === 1) {
continue;
}
$numericPart = substr($string, 0, -$postfixLength);
if (!is_numeric($numericPart)) {
throw new InvalidArgumentException("Incorrect input string: $string");
}
$postfixMultiplier = $postfixes[$postfix] ?? null;
if ($postfixMultiplier === null) {
throw new InvalidArgumentException("Not supported postfix '$postfix' in input string: $string");
}
return (int) ((float) $numericPart * $postfixMultiplier);
}
throw new InvalidArgumentException("Incorrect input string: $string");
}
/**
* Trims trailing decimal zeros from a numeric-like string.
*
* If the fractional part consists only of zeros, the decimal separator is removed as well.
* The value that is `null` or empty is returned as-is.
*
* @param string|null $value String representation of a number or any string potentially
* containing a decimal part.
*
* @return string|null The input string with trailing decimal zeros (and a trailing decimal
* separator, if any) removed, or `null` if the input was `null` or an empty string.
*/
public static function trimDecimalZeros(?string $value): ?string
{
if ($value === null) {
return null;
}
$value = trim($value);
if ($value === '') {
return null;
}
if (!str_contains($value, '.')) {
return $value;
}
/** @psalm-suppress PossiblyNullArgument */
$value = rtrim($value, '0');
if ($value === '' || !str_ends_with($value, '.')) {
return $value;
}
return substr($value, 0, -1);
}
}