diff --git a/src/support/src/Number.php b/src/support/src/Number.php index ecbb187f2..b2881d81c 100644 --- a/src/support/src/Number.php +++ b/src/support/src/Number.php @@ -31,7 +31,6 @@ class Number /** * Format the given number according to the current locale. * - * @param ?string $locale * @return string|false */ public static function format(int|float $number, ?int $precision = null, ?int $maxPrecision = null, ?string $locale = null) @@ -52,7 +51,6 @@ public static function format(int|float $number, ?int $precision = null, ?int $m /** * Spell out the given number in the given locale. * - * @param ?string $locale * @return string */ public static function spell(int|float $number, ?string $locale = null) @@ -67,7 +65,6 @@ public static function spell(int|float $number, ?string $locale = null) /** * Convert the given number to ordinal form. * - * @param ?string $locale * @return string */ public static function ordinal(int|float $number, ?string $locale = null) @@ -82,7 +79,6 @@ public static function ordinal(int|float $number, ?string $locale = null) /** * Convert the given number to its percentage equivalent. * - * @param ?string $locale * @return string|false */ public static function percentage(int|float $number, int $precision = 0, ?int $maxPrecision = null, ?string $locale = null) @@ -103,7 +99,6 @@ public static function percentage(int|float $number, int $precision = 0, ?int $m /** * Convert the given number to its currency equivalent. * - * @param ?string $locale * @return string|false */ public static function currency(int|float $number, string $in = 'USD', ?string $locale = null) @@ -137,30 +132,32 @@ public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxP * @param int $number * @return string */ - public static function forHumans(int|float $number, int $precision = 0, ?int $maxPrecision = null) + public static function abbreviate(int|float $number, int $precision = 0, ?int $maxPrecision = null) { - $units = [ - 3 => 'thousand', - 6 => 'million', - 9 => 'billion', - 12 => 'trillion', - 15 => 'quadrillion', - ]; - - switch (true) { - case $number === 0: - return '0'; - case $number < 0: - return sprintf('-%s', static::forHumans(abs($number), $precision, $maxPrecision)); - case $number >= 1e15: - return sprintf('%s quadrillion', static::forHumans($number / 1e15, $precision, $maxPrecision)); - } - - $numberExponent = floor(log10($number)); - $displayExponent = $numberExponent - ($numberExponent % 3); - $number /= pow(10, $displayExponent); + return static::forHumans($number, $precision, $maxPrecision, abbreviate: true); + } - return trim(sprintf('%s %s', static::format($number, $precision, $maxPrecision), $units[$displayExponent] ?? '')); + /** + * Convert the number to its human readable equivalent. + * + * @param int $number + * @return string + */ + public static function forHumans(int|float $number, int $precision = 0, ?int $maxPrecision = null, bool $abbreviate = false) + { + return static::summarize($number, $precision, $maxPrecision, $abbreviate ? [ + 3 => 'K', + 6 => 'M', + 9 => 'B', + 12 => 'T', + 15 => 'Q', + ] : [ + 3 => ' thousand', + 6 => ' million', + 9 => ' billion', + 12 => ' trillion', + 15 => ' quadrillion', + ]); } /** @@ -186,7 +183,41 @@ public static function useLocale(string $locale) } /** - * Ensure the "intl" PHP extension is installed. + * Convert the number to its human readable equivalent. + * + * @param int $number + * @return string + */ + protected static function summarize(int|float $number, int $precision = 0, ?int $maxPrecision = null, array $units = []) + { + if (empty($units)) { + $units = [ + 3 => 'K', + 6 => 'M', + 9 => 'B', + 12 => 'T', + 15 => 'Q', + ]; + } + + switch (true) { + case $number === 0: + return '0'; + case $number < 0: + return sprintf('-%s', static::summarize(abs($number), $precision, $maxPrecision, $units)); + case $number >= 1e15: + return sprintf('%s' . end($units), static::summarize($number / 1e15, $precision, $maxPrecision, $units)); + } + + $numberExponent = floor(log10($number)); + $displayExponent = $numberExponent - ($numberExponent % 3); + $number /= pow(10, $displayExponent); + + return trim(sprintf('%s%s', static::format($number, $precision, $maxPrecision), $units[$displayExponent] ?? '')); + } + + /** + * Ensure the "intl" PHP exntension is installed. */ protected static function ensureIntlExtensionIsInstalled() { diff --git a/tests/Support/NumberTest.php b/tests/Support/NumberTest.php index dad663ba9..b6c422d8b 100644 --- a/tests/Support/NumberTest.php +++ b/tests/Support/NumberTest.php @@ -221,6 +221,59 @@ public function testToHuman() $this->assertSame('-1 thousand quadrillion', Number::forHumans(-1000000000000000000)); } + public function testSummarize() + { + $this->assertSame('1', Number::abbreviate(1)); + $this->assertSame('1.00', Number::abbreviate(1, precision: 2)); + $this->assertSame('10', Number::abbreviate(10)); + $this->assertSame('100', Number::abbreviate(100)); + $this->assertSame('1K', Number::abbreviate(1000)); + $this->assertSame('1.00K', Number::abbreviate(1000, precision: 2)); + $this->assertSame('1K', Number::abbreviate(1000, maxPrecision: 2)); + $this->assertSame('1K', Number::abbreviate(1230)); + $this->assertSame('1.2K', Number::abbreviate(1230, maxPrecision: 1)); + $this->assertSame('1M', Number::abbreviate(1000000)); + $this->assertSame('1B', Number::abbreviate(1000000000)); + $this->assertSame('1T', Number::abbreviate(1000000000000)); + $this->assertSame('1Q', Number::abbreviate(1000000000000000)); + $this->assertSame('1KQ', Number::abbreviate(1000000000000000000)); + + $this->assertSame('123', Number::abbreviate(123)); + $this->assertSame('1K', Number::abbreviate(1234)); + $this->assertSame('1.23K', Number::abbreviate(1234, precision: 2)); + $this->assertSame('12K', Number::abbreviate(12345)); + $this->assertSame('1M', Number::abbreviate(1234567)); + $this->assertSame('1B', Number::abbreviate(1234567890)); + $this->assertSame('1T', Number::abbreviate(1234567890123)); + $this->assertSame('1.23T', Number::abbreviate(1234567890123, precision: 2)); + $this->assertSame('1Q', Number::abbreviate(1234567890123456)); + $this->assertSame('1.23KQ', Number::abbreviate(1234567890123456789, precision: 2)); + $this->assertSame('490K', Number::abbreviate(489939)); + $this->assertSame('489.9390K', Number::abbreviate(489939, precision: 4)); + $this->assertSame('500.00000M', Number::abbreviate(500000000, precision: 5)); + + $this->assertSame('1MQ', Number::abbreviate(1000000000000000000000)); + $this->assertSame('1BQ', Number::abbreviate(1000000000000000000000000)); + $this->assertSame('1TQ', Number::abbreviate(1000000000000000000000000000)); + $this->assertSame('1QQ', Number::abbreviate(1000000000000000000000000000000)); + $this->assertSame('1KQQ', Number::abbreviate(1000000000000000000000000000000000)); + + $this->assertSame('0', Number::abbreviate(0)); + $this->assertSame('-1', Number::abbreviate(-1)); + $this->assertSame('-1.00', Number::abbreviate(-1, precision: 2)); + $this->assertSame('-10', Number::abbreviate(-10)); + $this->assertSame('-100', Number::abbreviate(-100)); + $this->assertSame('-1K', Number::abbreviate(-1000)); + $this->assertSame('-1.23K', Number::abbreviate(-1234, precision: 2)); + $this->assertSame('-1.2K', Number::abbreviate(-1234, maxPrecision: 1)); + $this->assertSame('-1M', Number::abbreviate(-1000000)); + $this->assertSame('-1B', Number::abbreviate(-1000000000)); + $this->assertSame('-1T', Number::abbreviate(-1000000000000)); + $this->assertSame('-1.1T', Number::abbreviate(-1100000000000, maxPrecision: 1)); + $this->assertSame('-1Q', Number::abbreviate(-1000000000000000)); + $this->assertSame('-1KQ', Number::abbreviate(-1000000000000000000)); + } + protected function needsIntlExtension() { if (! extension_loaded('intl')) {