Number.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. /**
  3. * Cake Number Utility.
  4. *
  5. * Methods to make numbers more readable.
  6. *
  7. * PHP 5
  8. *
  9. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  10. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. *
  12. * Licensed under The MIT License
  13. * For full copyright and license information, please see the LICENSE.txt
  14. * Redistributions of files must retain the above copyright notice.
  15. *
  16. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  17. * @link http://cakephp.org CakePHP(tm) Project
  18. * @since CakePHP(tm) v 0.10.0.1076
  19. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  20. */
  21. namespace Cake\Utility;
  22. use Cake\Error;
  23. /**
  24. * Number helper library.
  25. *
  26. * Methods to make numbers more readable.
  27. *
  28. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html
  29. */
  30. class Number {
  31. /**
  32. * Currencies supported by the helper. You can add additional currency formats
  33. * with Cake\Utility\Number::addFormat
  34. *
  35. * @var array
  36. */
  37. protected static $_currencies = array(
  38. 'AUD' => array(
  39. 'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
  40. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  41. 'fractionExponent' => 2
  42. ),
  43. 'CAD' => array(
  44. 'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
  45. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  46. 'fractionExponent' => 2
  47. ),
  48. 'USD' => array(
  49. 'wholeSymbol' => '$', 'wholePosition' => 'before', 'fractionSymbol' => 'c', 'fractionPosition' => 'after',
  50. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  51. 'fractionExponent' => 2
  52. ),
  53. 'EUR' => array(
  54. 'wholeSymbol' => '€', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
  55. 'zero' => 0, 'places' => 2, 'thousands' => '.', 'decimals' => ',', 'negative' => '()', 'escape' => true,
  56. 'fractionExponent' => 0
  57. ),
  58. 'GBP' => array(
  59. 'wholeSymbol' => '£', 'wholePosition' => 'before', 'fractionSymbol' => 'p', 'fractionPosition' => 'after',
  60. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  61. 'fractionExponent' => 2
  62. ),
  63. 'JPY' => array(
  64. 'wholeSymbol' => '¥', 'wholePosition' => 'before', 'fractionSymbol' => false, 'fractionPosition' => 'after',
  65. 'zero' => 0, 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  66. 'fractionExponent' => 0
  67. ),
  68. );
  69. /**
  70. * Default options for currency formats
  71. *
  72. * @var array
  73. */
  74. protected static $_currencyDefaults = array(
  75. 'wholeSymbol' => '', 'wholePosition' => 'before', 'fractionSymbol' => '', 'fractionPosition' => 'after',
  76. 'zero' => '0', 'places' => 2, 'thousands' => ',', 'decimals' => '.', 'negative' => '()', 'escape' => true,
  77. 'fractionExponent' => 2
  78. );
  79. /**
  80. * Default currency used by CakeNumber::currency()
  81. *
  82. * @var string
  83. */
  84. protected static $_defaultCurrency = 'USD';
  85. /**
  86. * If native number_format() should be used. If >= PHP5.4
  87. *
  88. * @var boolean
  89. */
  90. protected static $_numberFormatSupport = null;
  91. /**
  92. * Formats a number with a level of precision.
  93. *
  94. * @param float $value A floating point number.
  95. * @param integer $precision The precision of the returned number.
  96. * @return float Formatted float.
  97. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::precision
  98. */
  99. public static function precision($value, $precision = 3) {
  100. return sprintf("%01.{$precision}f", $value);
  101. }
  102. /**
  103. * Returns a formatted-for-humans file size.
  104. *
  105. * @param integer $size Size in bytes
  106. * @return string Human readable size
  107. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
  108. */
  109. public static function toReadableSize($size) {
  110. switch (true) {
  111. case $size < 1024:
  112. return __dn('cake', '%d Byte', '%d Bytes', $size, $size);
  113. case round($size / 1024) < 1024:
  114. return __d('cake', '%s KB', static::precision($size / 1024, 0));
  115. case round($size / 1024 / 1024, 2) < 1024:
  116. return __d('cake', '%s MB', static::precision($size / 1024 / 1024, 2));
  117. case round($size / 1024 / 1024 / 1024, 2) < 1024:
  118. return __d('cake', '%s GB', static::precision($size / 1024 / 1024 / 1024, 2));
  119. default:
  120. return __d('cake', '%s TB', static::precision($size / 1024 / 1024 / 1024 / 1024, 2));
  121. }
  122. }
  123. /**
  124. * Converts filesize from human readable string to bytes
  125. *
  126. * @param string $size Size in human readable string like '5MB', '5M', '500B', '50kb' etc.
  127. * @param mixed $default Value to be returned when invalid size was used, for example 'Unknown type'
  128. * @return mixed Number of bytes as integer on success, `$default` on failure if not false
  129. * @throws Cake\Error\Exception On invalid Unit type.
  130. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::fromReadableSize
  131. */
  132. public static function fromReadableSize($size, $default = false) {
  133. if (ctype_digit($size)) {
  134. return (int)$size;
  135. }
  136. $size = strtoupper($size);
  137. $l = -2;
  138. $i = array_search(substr($size, -2), array('KB', 'MB', 'GB', 'TB', 'PB'));
  139. if ($i === false) {
  140. $l = -1;
  141. $i = array_search(substr($size, -1), array('K', 'M', 'G', 'T', 'P'));
  142. }
  143. if ($i !== false) {
  144. $size = substr($size, 0, $l);
  145. return $size * pow(1024, $i + 1);
  146. }
  147. if (substr($size, -1) === 'B' && ctype_digit(substr($size, 0, -1))) {
  148. $size = substr($size, 0, -1);
  149. return (int)$size;
  150. }
  151. if ($default !== false) {
  152. return $default;
  153. }
  154. throw new Error\Exception(__d('cake_dev', 'No unit type.'));
  155. }
  156. /**
  157. * Formats a number into a percentage string.
  158. *
  159. * Options:
  160. *
  161. * - `multiply`: Multiply the input value by 100 for decimal percentages.
  162. *
  163. * @param float $value A floating point number
  164. * @param integer $precision The precision of the returned number
  165. * @param array $options Options
  166. * @return string Percentage string
  167. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toPercentage
  168. */
  169. public static function toPercentage($value, $precision = 2, $options = array()) {
  170. $options += array('multiply' => false);
  171. if ($options['multiply']) {
  172. $value *= 100;
  173. }
  174. return static::precision($value, $precision) . '%';
  175. }
  176. /**
  177. * Formats a number into a currency format.
  178. *
  179. * @param float $value A floating point number
  180. * @param integer $options If integer then places, if string then before, if (,.-) then use it
  181. * or array with places and before keys
  182. * @return string formatted number
  183. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::format
  184. */
  185. public static function format($value, $options = false) {
  186. $places = 0;
  187. if (is_int($options)) {
  188. $places = $options;
  189. }
  190. $separators = array(',', '.', '-', ':');
  191. $before = $after = null;
  192. if (is_string($options) && !in_array($options, $separators)) {
  193. $before = $options;
  194. }
  195. $thousands = ',';
  196. if (!is_array($options) && in_array($options, $separators)) {
  197. $thousands = $options;
  198. }
  199. $decimals = '.';
  200. if (!is_array($options) && in_array($options, $separators)) {
  201. $decimals = $options;
  202. }
  203. $escape = true;
  204. if (is_array($options)) {
  205. $defaults = array('before' => '$', 'places' => 2, 'thousands' => ',', 'decimals' => '.');
  206. $options += $defaults;
  207. extract($options);
  208. }
  209. $out = $before . number_format($value, $places, $decimals, $thousands) . $after;
  210. if ($escape) {
  211. return h($out);
  212. }
  213. return $out;
  214. }
  215. /**
  216. * Formats a number into a currency format to show deltas (signed differences in value).
  217. *
  218. * ### Options
  219. *
  220. * - `places` - Number of decimal places to use. ie. 2
  221. * - `fractionExponent` - Fraction exponent of this specific currency. Defaults to 2.
  222. * - `before` - The string to place before whole numbers. ie. '['
  223. * - `after` - The string to place after decimal numbers. ie. ']'
  224. * - `thousands` - Thousands separator ie. ','
  225. * - `decimals` - Decimal separator symbol ie. '.'
  226. *
  227. * @param float $value A floating point number
  228. * @param array $options
  229. * @return string formatted delta
  230. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::formatDelta
  231. */
  232. public static function formatDelta($value, $options = array()) {
  233. $places = isset($options['places']) ? $options['places'] : 0;
  234. $value = number_format($value, $places, '.', '');
  235. $sign = $value > 0 ? '+' : '';
  236. $options['before'] = isset($options['before']) ? $options['before'] . $sign : $sign;
  237. return static::format($value, $options);
  238. }
  239. /**
  240. * Formats a number into a currency format.
  241. *
  242. * ### Options
  243. *
  244. * - `wholeSymbol` - The currency symbol to use for whole numbers,
  245. * greater than 1, or less than -1.
  246. * - `wholePosition` - The position the whole symbol should be placed
  247. * valid options are 'before' & 'after'.
  248. * - `fractionSymbol` - The currency symbol to use for fractional numbers.
  249. * - `fractionPosition` - The position the fraction symbol should be placed
  250. * valid options are 'before' & 'after'.
  251. * - `before` - The currency symbol to place before whole numbers
  252. * ie. '$'. `before` is an alias for `wholeSymbol`.
  253. * - `after` - The currency symbol to place after decimal numbers
  254. * ie. 'c'. Set to boolean false to use no decimal symbol.
  255. * eg. 0.35 => $0.35. `after` is an alias for `fractionSymbol`
  256. * - `zero` - The text to use for zero values, can be a
  257. * string or a number. ie. 0, 'Free!'
  258. * - `places` - Number of decimal places to use. ie. 2
  259. * - `fractionExponent` - Fraction exponent of this specific currency. Defaults to 2.
  260. * - `thousands` - Thousands separator ie. ','
  261. * - `decimals` - Decimal separator symbol ie. '.'
  262. * - `negative` - Symbol for negative numbers. If equal to '()',
  263. * the number will be wrapped with ( and )
  264. * - `escape` - Should the output be escaped for html special characters.
  265. * The default value for this option is controlled by the currency settings.
  266. * By default all currencies contain utf-8 symbols and don't need this changed. If you require
  267. * non HTML encoded symbols you will need to update the settings with the correct bytes.
  268. *
  269. * @param float $value
  270. * @param string $currency Shortcut to default options. Valid values are
  271. * 'USD', 'EUR', 'GBP', otherwise set at least 'before' and 'after' options.
  272. * @param array $options
  273. * @return string Number formatted as a currency.
  274. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::currency
  275. */
  276. public static function currency($value, $currency = null, $options = array()) {
  277. $default = static::$_currencyDefaults;
  278. if ($currency === null) {
  279. $currency = static::defaultCurrency();
  280. }
  281. if (isset(static::$_currencies[$currency])) {
  282. $default = static::$_currencies[$currency];
  283. } elseif (is_string($currency)) {
  284. $options['before'] = $currency;
  285. }
  286. $options = array_merge($default, $options);
  287. if (isset($options['before']) && $options['before'] !== '') {
  288. $options['wholeSymbol'] = $options['before'];
  289. }
  290. if (isset($options['after']) && !$options['after'] !== '') {
  291. $options['fractionSymbol'] = $options['after'];
  292. }
  293. $result = $options['before'] = $options['after'] = null;
  294. $symbolKey = 'whole';
  295. $value = (float)$value;
  296. if (!$value) {
  297. if ($options['zero'] !== 0) {
  298. return $options['zero'];
  299. }
  300. } elseif ($value < 1 && $value > -1) {
  301. if ($options['fractionSymbol'] !== false) {
  302. $multiply = pow(10, $options['fractionExponent']);
  303. $value = $value * $multiply;
  304. $options['places'] = null;
  305. $symbolKey = 'fraction';
  306. }
  307. }
  308. $position = $options[$symbolKey . 'Position'] !== 'after' ? 'before' : 'after';
  309. $options[$position] = $options[$symbolKey . 'Symbol'];
  310. $abs = abs($value);
  311. $result = static::format($abs, $options);
  312. if ($value < 0) {
  313. if ($options['negative'] === '()') {
  314. $result = '(' . $result . ')';
  315. } else {
  316. $result = $options['negative'] . $result;
  317. }
  318. }
  319. return $result;
  320. }
  321. /**
  322. * Add a currency format to the Number helper. Makes reusing
  323. * currency formats easier.
  324. *
  325. * {{{ $number->addFormat('NOK', array('before' => 'Kr. ')); }}}
  326. *
  327. * You can now use `NOK` as a shortform when formatting currency amounts.
  328. *
  329. * {{{ $number->currency($value, 'NOK'); }}}
  330. *
  331. * Added formats are merged with the defaults defined in Cake\Utility\Number::$_currencyDefaults
  332. * See Cake\Utility\Number::currency() for more information on the various options and their function.
  333. *
  334. * @param string $formatName The format name to be used in the future.
  335. * @param array $options The array of options for this format.
  336. * @return void
  337. * @see NumberHelper::currency()
  338. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::addFormat
  339. */
  340. public static function addFormat($formatName, $options) {
  341. static::$_currencies[$formatName] = $options + static::$_currencyDefaults;
  342. }
  343. /**
  344. * Getter/setter for default currency
  345. *
  346. * @param string $currency Default currency string used by currency() if $currency argument is not provided
  347. * @return string Currency
  348. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::defaultCurrency
  349. */
  350. public static function defaultCurrency($currency = null) {
  351. if ($currency) {
  352. self::$_defaultCurrency = $currency;
  353. }
  354. return self::$_defaultCurrency;
  355. }
  356. }