Skip to content

Commit f52e6bb

Browse files
committed
add NumberFormatter
1 parent 618be5f commit f52e6bb

File tree

7 files changed

+174
-80
lines changed

7 files changed

+174
-80
lines changed

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"license": "MIT",
55
"type": "library",
66
"require": {
7-
"php": ">=7.4"
7+
"php": ">=8.0"
88
},
99
"require-dev": {
1010
"phpstan/phpstan": "^0.12.33",

src/Money.php

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,17 @@
22

33
namespace Utilitte\Php;
44

5+
use JetBrains\PhpStorm\Deprecated;
6+
use Utilitte\Php\Numbers\NumberFormatter;
7+
8+
#[Deprecated]
59
final class Money
610
{
711

12+
#[Deprecated]
813
public static function formatShort(int $value, int $precision = 1): string
914
{
10-
// 1_000
11-
if ($value < 1000) {
12-
return self::formatMoney($value, $precision);
13-
} elseif ($value < 1000000) {
14-
// 1_000_000
15-
return self::formatMoney($value / 1000, $precision) . 'K';
16-
} elseif ($value < 1000000000) {
17-
// 1_000_000_000
18-
return self::formatMoney($value / 1000000, $precision) . 'M';
19-
} elseif ($value < 1000000000000) {
20-
// 1_000_000_000_000
21-
return self::formatMoney($value / 1000000000, $precision) . 'B';
22-
}
23-
24-
return self::formatMoney($value / 1000000000000, $precision) . 'T';
25-
}
26-
27-
private static function formatMoney(float $value, int $precision): string
28-
{
29-
return rtrim(number_format($value, $precision), '0.');
15+
return NumberFormatter::formatShort($value, $precision);
3016
}
3117

3218
}

src/Numbers.php

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
namespace Utilitte\Php;
44

5+
use JetBrains\PhpStorm\Deprecated;
6+
use Utilitte\Php\Numbers\NumberFormatter;
7+
58
final class Numbers
69
{
710

@@ -10,46 +13,16 @@ public static function minMax(int $value, int $min, int $max): int
1013
return min(max($value, $min), $max);
1114
}
1215

16+
#[Deprecated]
1317
public static function bytes(float $bytes, int $precision = 2, bool $withSpace = true): string
1418
{
15-
$bytes = round($bytes);
16-
$units = ['B', 'kB', 'MB', 'GB', 'TB', $end = 'PB'];
17-
18-
foreach ($units as $unit) {
19-
if (abs($bytes) < 1024 || $unit === $end) {
20-
break;
21-
}
22-
23-
$bytes /= 1024;
24-
}
25-
26-
return round($bytes, $precision) . ($withSpace ? ' ' : '') . $unit;
19+
return NumberFormatter::formatBytes($bytes, $precision);
2720
}
2821

22+
#[Deprecated]
2923
public static function formatShort(int $value, int $precision = 1): string
3024
{
31-
// 1_000
32-
if ($value < 1000) {
33-
return self::removeUnnecessaryDotsZeros($value, $precision);
34-
} elseif ($value < 1000000) {
35-
// 1_000_000
36-
return self::removeUnnecessaryDotsZeros($value / 1000, $precision) . 'K';
37-
} elseif ($value < 1000000000) {
38-
// 1_000_000_000
39-
return self::removeUnnecessaryDotsZeros($value / 1000000, $precision) . 'M';
40-
} elseif ($value < 1000000000000) {
41-
// 1_000_000_000_000
42-
return self::removeUnnecessaryDotsZeros($value / 1000000000, $precision) . 'B';
43-
}
44-
45-
return self::removeUnnecessaryDotsZeros($value / 1000000000000, $precision) . 'T';
46-
}
47-
48-
private static function removeUnnecessaryDotsZeros(float $value, int $precision): string
49-
{
50-
$value = rtrim(number_format($value, $precision), '0.');
51-
52-
return $value === '' ? '0' : $value;
25+
return NumberFormatter::formatShort($value, $precision);
5326
}
5427

5528
}

src/Numbers/NumberFormatter.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Utilitte\Php\Numbers;
4+
5+
use LogicException;
6+
7+
final class NumberFormatter
8+
{
9+
10+
public static function formatBytes(int|float $number, ?int $decimals = null, bool $fixed = false): string
11+
{
12+
static $end = 'PB';
13+
static $units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
14+
15+
$unit = 'B';
16+
foreach ($units as $unit) {
17+
if (abs($number) < 1024 || $unit === $end) {
18+
break;
19+
}
20+
21+
$number /= 1024;
22+
}
23+
24+
return self::formatNumber($number, $decimals, $fixed). $unit;
25+
}
26+
27+
public static function formatShort(int|float $number, ?int $decimals = null, bool $fixed = false): string
28+
{
29+
static $formatters = [
30+
'' => 1_000,
31+
'K' => 1_000_000,
32+
'M' => 1_000_000_000,
33+
'B' => 1_000_000_000_000,
34+
'T' => null,
35+
];
36+
37+
$divider = 1;
38+
foreach ($formatters as $str => $limit) {
39+
if ($limit === null || $number < $limit) {
40+
return self::formatNumber($number / $divider, $decimals, $fixed) . $str;
41+
}
42+
43+
$divider = $limit;
44+
}
45+
46+
throw new LogicException('Unexpected error.');
47+
}
48+
49+
public static function formatPercentage(
50+
string|int|float|null $number,
51+
int $decimals = 2,
52+
bool $sign = true,
53+
bool $fixed = true,
54+
): ?string
55+
{
56+
if ($number === null || is_string($number) && !is_numeric($number)) {
57+
return null;
58+
}
59+
60+
$value = self::formatNumber($number, $decimals, $fixed);
61+
62+
if ($value === null) {
63+
return null;
64+
}
65+
66+
return ($sign && $number > 0 ? '+' : '') . $value . '%';
67+
}
68+
69+
public static function formatNumber(string|int|float|null $number, ?int $decimals = null, bool $fixed = false): ?string
70+
{
71+
if (!is_numeric($number)) {
72+
return null;
73+
}
74+
75+
$number = (float) $number;
76+
77+
if (is_nan($number) || is_infinite($number)) {
78+
return null;
79+
}
80+
81+
if ($decimals === null) {
82+
$decimals = 2;
83+
84+
if ($number < 0.01) {
85+
$decimals = 6;
86+
} elseif ($number < 0.1) {
87+
$decimals = 5;
88+
} elseif ($number < 1) {
89+
$decimals = 4;
90+
} elseif ($number < 10) {
91+
$decimals = 3;
92+
}
93+
}
94+
95+
$number = number_format($number, $decimals);
96+
97+
if (!$fixed) {
98+
$number = self::removeZerosAfterDot($number);
99+
}
100+
101+
return $number;
102+
}
103+
104+
public static function removeZerosAfterDot(string $number): string
105+
{
106+
return rtrim(rtrim($number, '0'), '.');
107+
}
108+
109+
}

tests/cases/money.formatShort.phpt

Lines changed: 0 additions & 11 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php declare(strict_types = 1);
2+
3+
use Tester\Assert;
4+
use Utilitte\Php\Numbers\NumberFormatter;
5+
6+
require __DIR__ . '/../bootstrap.php';
7+
8+
// non-fixed
9+
Assert::same('0.2', NumberFormatter::formatNumber(0.2));
10+
Assert::same('10', NumberFormatter::formatNumber(10));
11+
Assert::same('-10', NumberFormatter::formatNumber(-10));
12+
Assert::same('10', NumberFormatter::formatNumber('10'));
13+
Assert::same('-10', NumberFormatter::formatNumber('-10'));
14+
Assert::same('10', NumberFormatter::formatNumber('10', 2));
15+
Assert::same('10.25', NumberFormatter::formatNumber('10.254', 2));
16+
Assert::same('10.26', NumberFormatter::formatNumber('10.255', 2));
17+
18+
// fixed
19+
Assert::same('0.2000', NumberFormatter::formatNumber(0.2, fixed: true));
20+
Assert::same('0.20', NumberFormatter::formatNumber(0.2, 2, true));
21+
22+
// invalid
23+
Assert::null(NumberFormatter::formatNumber('foo'));
24+
Assert::null(NumberFormatter::formatNumber(INF));
25+
Assert::null(NumberFormatter::formatNumber(NAN));
26+
Assert::null(NumberFormatter::formatNumber('0,2'));
27+
Assert::null(NumberFormatter::formatNumber('a0.2'));
28+
29+
// percentage
30+
Assert::same('+0.20%',NumberFormatter::formatPercentage(0.2));
31+
Assert::same('+100.00%',NumberFormatter::formatPercentage(100));
32+
Assert::same('100.00%',NumberFormatter::formatPercentage(100, sign: false));
33+
Assert::same('+100%',NumberFormatter::formatPercentage(100, fixed: false));
34+
Assert::same('-20.00%',NumberFormatter::formatPercentage(-20));
35+
36+
// formatShort
37+
Assert::same('500', NumberFormatter::formatShort(500));
38+
Assert::same('1K', NumberFormatter::formatShort(1000));
39+
Assert::same('20K', NumberFormatter::formatShort(20000));
40+
Assert::same('1.3K', NumberFormatter::formatShort(1254, 1));
41+
Assert::same('1.254K', NumberFormatter::formatShort(1254, 3));
42+
Assert::same('1M', NumberFormatter::formatShort(1000*1000));
43+
44+
// formatBytes
45+
Assert::same('1kB', NumberFormatter::formatBytes(1024));
46+
Assert::same('1MB', NumberFormatter::formatBytes(1024 * 1024));
47+
Assert::same('1GB', NumberFormatter::formatBytes(1024 * 1024 * 1024));
48+
Assert::same('1TB', NumberFormatter::formatBytes(1024 * 1024 * 1024 * 1024));
49+
Assert::same('1PB', NumberFormatter::formatBytes(1024 * 1024 * 1024 * 1024 * 1024));
50+
Assert::same('2.1PB', NumberFormatter::formatBytes(1024 * 1024 * 1024 * 1024 * 1024 * 2.1));
51+
Assert::same('2.234PB', NumberFormatter::formatBytes(1024 * 1024 * 1024 * 1024 * 1024 * 2.234));

tests/cases/numbers.bytes.phpt

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)