From 73dea41565f2e22f3792a2cf0176271a568afc28 Mon Sep 17 00:00:00 2001 From: Aleksei Lebedev <1329824+LastDragon-ru@users.noreply.github.com> Date: Tue, 12 Dec 2023 09:08:28 +0400 Subject: [PATCH] feat(formatter): More precise `Formatter::filesize()`/`Formatter::disksize()` (`1047552` bytes will be converted to "1.00 MiB" instead of "1,023.00 KiB"). --- composer.json | 1 + packages/formatter/composer.json | 1 + packages/formatter/src/Formatter.php | 24 ++++++++++++++++++------ packages/formatter/src/FormatterTest.php | 8 ++++++++ 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 99a327a6a..37ad7a540 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,7 @@ }, "require": { "php": "^8.1|^8.2|^8.3", + "ext-bcmath": "*", "ext-dom": "*", "ext-intl": "*", "ext-json": "*", diff --git a/packages/formatter/composer.json b/packages/formatter/composer.json index 8c3b360be..4a7aea5af 100644 --- a/packages/formatter/composer.json +++ b/packages/formatter/composer.json @@ -19,6 +19,7 @@ }, "require": { "php": "^8.1|^8.2|^8.3", + "ext-bcmath": "*", "ext-intl": "*", "ext-mbstring": "*", "laravel/framework": "^9.21.0|^10.0.0", diff --git a/packages/formatter/src/Formatter.php b/packages/formatter/src/Formatter.php index 33cb7d416..8f99bfbb5 100644 --- a/packages/formatter/src/Formatter.php +++ b/packages/formatter/src/Formatter.php @@ -7,6 +7,7 @@ use DateTimeZone; use Illuminate\Container\Container; use Illuminate\Contracts\Foundation\Application; +use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use IntlDateFormatter; use IntlTimeZone; @@ -19,7 +20,10 @@ use OutOfBoundsException; use function abs; +use function bccomp; +use function bcdiv; use function config; +use function is_float; use function is_int; use function is_null; use function is_string; @@ -494,18 +498,26 @@ protected function formatDateTime( * @param array, string> $units */ protected function formatFilesize(string|float|int|null $bytes, int $decimals, int $base, array $units): string { - $bytes = (float) $bytes; $unit = 0; - - while ($bytes >= $base) { - $bytes /= $base; + $base = (string) $base; + $scale = 2 * $decimals; + $bytes = match (true) { + is_float($bytes) => sprintf('%0.0f', $bytes), + default => (string) $bytes, + }; + $length = static function (string $bytes): int { + return mb_strlen(Str::before($bytes, '.')); + }; + + while ((bccomp($bytes, $base, $scale) >= 0 || $length($bytes) > 2) && isset($units[$unit + 1])) { + $bytes = bcdiv($bytes, $base, $scale); $unit++; } // Format return $unit === 0 - ? $this->integer($bytes)." {$units[$unit]}" - : $this->decimal($bytes, $decimals)." {$units[$unit]}"; + ? $this->integer((int) $bytes)." {$units[$unit]}" + : $this->decimal((float) $bytes, $decimals)." {$units[$unit]}"; } // diff --git a/packages/formatter/src/FormatterTest.php b/packages/formatter/src/FormatterTest.php index b201e02ea..2fa2b4ff2 100644 --- a/packages/formatter/src/FormatterTest.php +++ b/packages/formatter/src/FormatterTest.php @@ -269,8 +269,10 @@ public function testFilesize(): void { self::assertEquals('0 B', $this->formatter->filesize(null)); self::assertEquals('0 B', $this->formatter->filesize(0)); self::assertEquals('10 B', $this->formatter->filesize(10)); + self::assertEquals('1.00 MiB', $this->formatter->filesize(1023 * 1024, 2)); self::assertEquals('10.33 MiB', $this->formatter->filesize(10 * 1024 * 1024 + 1024 * 334)); self::assertEquals('10.00 GiB', $this->formatter->filesize(10 * 1024 * 1024 * 1024, 2)); + self::assertEquals('0.87 EiB', $this->formatter->filesize(999_999_999_999_999_999, 2)); self::assertEquals('8.00 EiB', $this->formatter->filesize(PHP_INT_MAX, 2)); } @@ -278,11 +280,17 @@ public function testDisksize(): void { self::assertEquals('0 B', $this->formatter->disksize(null)); self::assertEquals('0 B', $this->formatter->disksize(0)); self::assertEquals('10 B', $this->formatter->disksize(10)); + self::assertEquals('1.00 MB', $this->formatter->disksize(999 * 1000, 2)); self::assertEquals('10.83 MB', $this->formatter->disksize(10 * 1024 * 1024 + 1024 * 334)); self::assertEquals('10.00 GB', $this->formatter->disksize(10 * 1000 * 1000 * 1000, 2)); self::assertEquals('9.22 EB', $this->formatter->disksize(PHP_INT_MAX, 2)); self::assertEquals('10.00 QB', $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000, 2)); self::assertEquals('10.00 QB', $this->formatter->disksize('10000000000000000000000000000000', 2)); + self::assertEquals('100.00 QB', $this->formatter->disksize('99999999999999999999999999999999', 2)); + self::assertEquals( + '10,000.00 QB', + $this->formatter->disksize(10_000_000_000_000_000_000_000_000_000_000_000, 2), + ); } public function testCurrency(): void {