Number.php 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. <?php
  2. namespace Illuminate\Support;
  3. use Illuminate\Support\Traits\Macroable;
  4. use NumberFormatter;
  5. use RuntimeException;
  6. class Number
  7. {
  8. use Macroable;
  9. /**
  10. * The current default locale.
  11. *
  12. * @var string
  13. */
  14. protected static $locale = 'en';
  15. /**
  16. * Format the given number according to the current locale.
  17. *
  18. * @param int|float $number
  19. * @param int|null $precision
  20. * @param int|null $maxPrecision
  21. * @param string|null $locale
  22. * @return string|false
  23. */
  24. public static function format(int|float $number, ?int $precision = null, ?int $maxPrecision = null, ?string $locale = null)
  25. {
  26. static::ensureIntlExtensionIsInstalled();
  27. $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::DECIMAL);
  28. if (! is_null($maxPrecision)) {
  29. $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $maxPrecision);
  30. } elseif (! is_null($precision)) {
  31. $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
  32. }
  33. return $formatter->format($number);
  34. }
  35. /**
  36. * Spell out the given number in the given locale.
  37. *
  38. * @param int|float $number
  39. * @param string|null $locale
  40. * @param int|null $after
  41. * @param int|null $until
  42. * @return string
  43. */
  44. public static function spell(int|float $number, ?string $locale = null, ?int $after = null, ?int $until = null)
  45. {
  46. static::ensureIntlExtensionIsInstalled();
  47. if (! is_null($after) && $number <= $after) {
  48. return static::format($number, locale: $locale);
  49. }
  50. if (! is_null($until) && $number >= $until) {
  51. return static::format($number, locale: $locale);
  52. }
  53. $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::SPELLOUT);
  54. return $formatter->format($number);
  55. }
  56. /**
  57. * Convert the given number to ordinal form.
  58. *
  59. * @param int|float $number
  60. * @param string|null $locale
  61. * @return string
  62. */
  63. public static function ordinal(int|float $number, ?string $locale = null)
  64. {
  65. static::ensureIntlExtensionIsInstalled();
  66. $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::ORDINAL);
  67. return $formatter->format($number);
  68. }
  69. /**
  70. * Convert the given number to its percentage equivalent.
  71. *
  72. * @param int|float $number
  73. * @param int $precision
  74. * @param int|null $maxPrecision
  75. * @param string|null $locale
  76. * @return string|false
  77. */
  78. public static function percentage(int|float $number, int $precision = 0, ?int $maxPrecision = null, ?string $locale = null)
  79. {
  80. static::ensureIntlExtensionIsInstalled();
  81. $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::PERCENT);
  82. if (! is_null($maxPrecision)) {
  83. $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $maxPrecision);
  84. } else {
  85. $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
  86. }
  87. return $formatter->format($number / 100);
  88. }
  89. /**
  90. * Convert the given number to its currency equivalent.
  91. *
  92. * @param int|float $number
  93. * @param string $in
  94. * @param string|null $locale
  95. * @return string|false
  96. */
  97. public static function currency(int|float $number, string $in = 'USD', ?string $locale = null)
  98. {
  99. static::ensureIntlExtensionIsInstalled();
  100. $formatter = new NumberFormatter($locale ?? static::$locale, NumberFormatter::CURRENCY);
  101. return $formatter->formatCurrency($number, $in);
  102. }
  103. /**
  104. * Convert the given number to its file size equivalent.
  105. *
  106. * @param int|float $bytes
  107. * @param int $precision
  108. * @param int|null $maxPrecision
  109. * @return string
  110. */
  111. public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxPrecision = null)
  112. {
  113. $units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
  114. for ($i = 0; ($bytes / 1024) > 0.9 && ($i < count($units) - 1); $i++) {
  115. $bytes /= 1024;
  116. }
  117. return sprintf('%s %s', static::format($bytes, $precision, $maxPrecision), $units[$i]);
  118. }
  119. /**
  120. * Convert the number to its human-readable equivalent.
  121. *
  122. * @param int|float $number
  123. * @param int $precision
  124. * @param int|null $maxPrecision
  125. * @return bool|string
  126. */
  127. public static function abbreviate(int|float $number, int $precision = 0, ?int $maxPrecision = null)
  128. {
  129. return static::forHumans($number, $precision, $maxPrecision, abbreviate: true);
  130. }
  131. /**
  132. * Convert the number to its human-readable equivalent.
  133. *
  134. * @param int|float $number
  135. * @param int $precision
  136. * @param int|null $maxPrecision
  137. * @param bool $abbreviate
  138. * @return bool|string
  139. */
  140. public static function forHumans(int|float $number, int $precision = 0, ?int $maxPrecision = null, bool $abbreviate = false)
  141. {
  142. return static::summarize($number, $precision, $maxPrecision, $abbreviate ? [
  143. 3 => 'K',
  144. 6 => 'M',
  145. 9 => 'B',
  146. 12 => 'T',
  147. 15 => 'Q',
  148. ] : [
  149. 3 => ' thousand',
  150. 6 => ' million',
  151. 9 => ' billion',
  152. 12 => ' trillion',
  153. 15 => ' quadrillion',
  154. ]);
  155. }
  156. /**
  157. * Convert the number to its human-readable equivalent.
  158. *
  159. * @param int|float $number
  160. * @param int $precision
  161. * @param int|null $maxPrecision
  162. * @param array $units
  163. * @return string|false
  164. */
  165. protected static function summarize(int|float $number, int $precision = 0, ?int $maxPrecision = null, array $units = [])
  166. {
  167. if (empty($units)) {
  168. $units = [
  169. 3 => 'K',
  170. 6 => 'M',
  171. 9 => 'B',
  172. 12 => 'T',
  173. 15 => 'Q',
  174. ];
  175. }
  176. switch (true) {
  177. case floatval($number) === 0.0:
  178. return $precision > 0 ? static::format(0, $precision, $maxPrecision) : '0';
  179. case $number < 0:
  180. return sprintf('-%s', static::summarize(abs($number), $precision, $maxPrecision, $units));
  181. case $number >= 1e15:
  182. return sprintf('%s'.end($units), static::summarize($number / 1e15, $precision, $maxPrecision, $units));
  183. }
  184. $numberExponent = floor(log10($number));
  185. $displayExponent = $numberExponent - ($numberExponent % 3);
  186. $number /= pow(10, $displayExponent);
  187. return trim(sprintf('%s%s', static::format($number, $precision, $maxPrecision), $units[$displayExponent] ?? ''));
  188. }
  189. /**
  190. * Clamp the given number between the given minimum and maximum.
  191. *
  192. * @param int|float $number
  193. * @param int|float $min
  194. * @param int|float $max
  195. * @return int|float
  196. */
  197. public static function clamp(int|float $number, int|float $min, int|float $max)
  198. {
  199. return min(max($number, $min), $max);
  200. }
  201. /**
  202. * Execute the given callback using the given locale.
  203. *
  204. * @param string $locale
  205. * @param callable $callback
  206. * @return mixed
  207. */
  208. public static function withLocale(string $locale, callable $callback)
  209. {
  210. $previousLocale = static::$locale;
  211. static::useLocale($locale);
  212. return tap($callback(), fn () => static::useLocale($previousLocale));
  213. }
  214. /**
  215. * Set the default locale.
  216. *
  217. * @param string $locale
  218. * @return void
  219. */
  220. public static function useLocale(string $locale)
  221. {
  222. static::$locale = $locale;
  223. }
  224. /**
  225. * Ensure the "intl" PHP extension is installed.
  226. *
  227. * @return void
  228. */
  229. protected static function ensureIntlExtensionIsInstalled()
  230. {
  231. if (! extension_loaded('intl')) {
  232. $method = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]['function'];
  233. throw new RuntimeException('The "intl" PHP extension is required to use the ['.$method.'] method.');
  234. }
  235. }
  236. }