NumberLib.php 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. <?php
  2. App::uses('CakeNumber', 'Utility');
  3. /**
  4. * Extend CakeNumber with a few important improvements:
  5. * - config setting for format()
  6. * - spacer char for currency (initially from https://github.com/cakephp/cakephp/pull/1148)
  7. * - signed values possible
  8. *
  9. * 2011-03-07 ms
  10. */
  11. class NumberLib extends CakeNumber {
  12. protected static $_currency = 'EUR';
  13. protected static $_symbolRight = '€';
  14. protected static $_symbolLeft = '';
  15. protected static $_decimals = ',';
  16. protected static $_thousands = '.';
  17. /**
  18. * Correct the defaul values according to localization
  19. *
  20. * @return void
  21. */
  22. public static function config($options = array()) {
  23. $config = $options + (array)Configure::read('Localization');
  24. foreach ($config as $key => $value) {
  25. $key = '_' . $key;
  26. if (!isset(self::${$key})) {
  27. continue;
  28. }
  29. self::${$key} = $value;
  30. }
  31. }
  32. /**
  33. * Display price (or was price if available)
  34. * Without allowNegative it will always default all non-positive values to 0
  35. *
  36. * @param price
  37. * @param specialPrice (outranks the price)
  38. * @param options
  39. * - places
  40. * - allowNegative (defaults to false - price needs to be > 0)
  41. *
  42. * @deprecated use currency()
  43. * @return string
  44. * 2011-07-30 ms
  45. */
  46. public static function price($price, $specialPrice = null, $formatOptions = array()) {
  47. if ($specialPrice !== null && $specialPrice > 0) {
  48. $val = $specialPrice;
  49. } elseif ($price > 0 || !empty($formatOptions['allowNegative'])) {
  50. $val = $price;
  51. } else {
  52. if (isset($formatOptions['default'])) {
  53. return $formatOptions['default'];
  54. }
  55. $val = max(0, $price);
  56. }
  57. return self::money($val, $formatOptions);
  58. }
  59. /**
  60. * Convenience method to display the default currency
  61. *
  62. * @param mixed $amount
  63. * @param array $formatOptions
  64. * @return string
  65. * 2011-10-05 ms
  66. */
  67. public static function money($amount, $formatOptions = array()) {
  68. return self::currency($amount, null, $formatOptions);
  69. }
  70. /**
  71. * format numeric values
  72. * should not be used for currencies
  73. * //TODO: automize per localeconv() ?
  74. *
  75. * @param float $number
  76. * @param integer $places (0 = int, 1..x places after dec, -1..-x places before dec)
  77. * @param array $option : currency=true/false, ... (leave empty for no special treatment)
  78. * @return string
  79. * 2009-04-03 ms
  80. */
  81. public static function format($number, $formatOptions = array()) {
  82. if (!is_numeric($number)) {
  83. $default = '---';
  84. if (!empty($options['default'])) {
  85. $default = $options['default'];
  86. }
  87. return $default;
  88. }
  89. if ($formatOptions === false) {
  90. $formatOptions = array();
  91. } elseif (!is_array($formatOptions)) {
  92. $formatOptions = array('places' => $formatOptions);
  93. }
  94. $options = array('before' => '', 'after' => '', 'places' => 2, 'thousands' => self::$_thousands, 'decimals' => self::$_decimals, 'escape' => false);
  95. $options = array_merge($options, $formatOptions);
  96. if (!empty($options['currency'])) {
  97. if (!empty(self::$_symbolRight)) {
  98. $options['after'] = ' ' . self::$_symbolRight;
  99. } elseif (!empty(self::$_symbolLeft)) {
  100. $options['before'] = self::$_symbolLeft . ' ';
  101. }
  102. }
  103. /*
  104. if ($spacer !== false) {
  105. $spacer = ($spacer === true) ? ' ' : $spacer;
  106. if ((string)$before !== '') {
  107. $before .= $spacer;
  108. }
  109. if ((string)$after !== '') {
  110. $after = $spacer . $after;
  111. }
  112. }
  113. */
  114. if ($options['places'] < 0) {
  115. $number = round($number, $options['places']);
  116. }
  117. $sign = '';
  118. if ($number > 0 && !empty($options['signed'])) {
  119. $sign = '+';
  120. }
  121. if (isset($options['signed'])) {
  122. unset($options['signed']);
  123. }
  124. return $sign . parent::format($number, $options);
  125. }
  126. /**
  127. * Correct the default for European countries
  128. *
  129. * @param mixed $number
  130. * @param string $currency
  131. * @param array $formatOptions
  132. * @return string
  133. * 2012-04-08 ms
  134. */
  135. public static function currency($number, $currency = null, $formatOptions = array()) {
  136. if ($currency === null) {
  137. $currency = self::$_currency;
  138. }
  139. $defaults = array();
  140. if ($currency !== 'EUR' && isset(self::$_currencies[$currency])) {
  141. $defaults = self::$_currencies[$currency];
  142. } elseif ($currency !== 'EUR' && is_string($currency)) {
  143. $defaults['wholeSymbol'] = $currency;
  144. $defaults['wholePosition'] = 'before';
  145. $defaults['spacer'] = true;
  146. }
  147. $defaults += array(
  148. 'wholeSymbol' => '€', 'wholePosition' => 'after',
  149. 'negative' => '-', 'positive'=> '+', 'escape' => true,
  150. 'decimals' => ',', 'thousands' => '.',
  151. 'spacer' => $currency === 'EUR' ? true : false
  152. );
  153. $options = array_merge($defaults, $formatOptions);
  154. if (!empty($options['spacer'])) {
  155. $spacer = is_string($options['spacer']) ? $options['spacer'] : ' ';
  156. if ($options['wholePosition'] === 'after') {
  157. $options['wholeSymbol'] = $spacer . $options['wholeSymbol'];
  158. } elseif ($options['wholePosition'] === 'before') {
  159. $options['wholeSymbol'] .= $spacer;
  160. }
  161. }
  162. $sign = '';
  163. if ($number > 0 && !empty($options['signed'])) {
  164. $sign = $options['positive'];
  165. }
  166. return $sign . parent::currency($number, null, $options);
  167. }
  168. /**
  169. * Formats a number with a level of precision.
  170. *
  171. * @param float $number A floating point number.
  172. * @param integer $precision The precision of the returned number.
  173. * @param string $decimals
  174. * @return float Formatted float.
  175. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::precision
  176. */
  177. public static function precision($number, $precision = 3, $decimals = '.') {
  178. $number = parent::precision($number, $precision);
  179. if ($decimals !== '.' && $precision > 0) {
  180. $number = str_replace('.', $decimals, $number);
  181. }
  182. return $number;
  183. }
  184. /**
  185. * Returns a formatted-for-humans file size.
  186. *
  187. * @param integer $size Size in bytes
  188. * @return string Human readable size
  189. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toReadableSize
  190. */
  191. public static function toReadableSize($size, $decimals = '.') {
  192. $size = parent::toReadableSize($size);
  193. if ($decimals !== '.') {
  194. $size = str_replace('.', $decimals, $size);
  195. }
  196. return $size;
  197. }
  198. /**
  199. * Formats a number into a percentage string.
  200. *
  201. * @param float $number A floating point number
  202. * @param integer $precision The precision of the returned number
  203. * @param string $decimals
  204. * @return string Percentage string
  205. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/number.html#NumberHelper::toPercentage
  206. */
  207. public static function toPercentage($number, $precision = 2, $decimals = '.') {
  208. return self::precision($number, $precision, $decimals) . '%';
  209. }
  210. /**
  211. * Get the rounded average.
  212. *
  213. * @param array $values: int or float values
  214. * @param integer $precision
  215. * @return integer $average
  216. * 2009-09-05 ms
  217. */
  218. public static function average($values, $precision = 0) {
  219. $average = round(array_sum($values) / count($values), $precision);
  220. return $average;
  221. }
  222. /**
  223. * Round value.
  224. *
  225. * @param float $number
  226. * @param float $increment
  227. * @return float $result
  228. * 2011-04-14 lb
  229. */
  230. public static function roundTo($number, $increments = 1.0) {
  231. $precision = self::getDecimalPlaces($increments);
  232. $res = round($number, $precision);
  233. if ($precision <= 0) {
  234. $res = (int)$res;
  235. }
  236. return $res;
  237. }
  238. /**
  239. * Round value up.
  240. *
  241. * @param float $number
  242. * @param integer $increment
  243. * @return float $result
  244. * 2011-04-14 lb
  245. */
  246. public static function roundUpTo($number, $increments = 1) {
  247. return (ceil($number / $increments) * $increments);
  248. }
  249. /**
  250. * Round value down.
  251. *
  252. * @param float $number
  253. * @param integer $increment
  254. * @return float $result
  255. * 2011-04-14 lb
  256. */
  257. public static function roundDownTo($number, $increments = 1) {
  258. return (floor($number / $increments) * $increments);
  259. }
  260. /**
  261. * Get decimal places
  262. *
  263. * @param float $number
  264. * @return integer $decimalPlaces
  265. * 2011-04-15 lb
  266. */
  267. public static function getDecimalPlaces($number) {
  268. $decimalPlaces = 0;
  269. while ($number > 1 && $number != 0) {
  270. $number /= 10;
  271. $decimalPlaces -= 1;
  272. }
  273. while ($number < 1 && $number != 0) {
  274. $number *= 10;
  275. $decimalPlaces += 1;
  276. }
  277. return $decimalPlaces;
  278. }
  279. /**
  280. * Returns the English ordinal suffix (th, st, nd, etc) of a number.
  281. *
  282. * echo Num::ordinal(2); // "2nd"
  283. * echo Num::ordinal(10); // "10th"
  284. * echo Num::ordinal(33); // "33rd"
  285. *
  286. * @param integer $number
  287. * @return string
  288. */
  289. public static function ordinal($number) {
  290. if ($number % 100 > 10 and $number % 100 < 14) {
  291. return 'th';
  292. }
  293. switch ($number % 10) {
  294. case 1:
  295. return 'st';
  296. case 2:
  297. return 'nd';
  298. case 3:
  299. return 'rd';
  300. default:
  301. return 'th';
  302. }
  303. }
  304. /**
  305. * Can compare two float values
  306. *
  307. * @link http://php.net/manual/en/language.types.float.php
  308. * @param float $x
  309. * @param float $y
  310. * @param float $precision
  311. * @return boolean
  312. */
  313. public static function isFloatEqual($x, $y, $precision = 0.0000001) {
  314. return ($x + $precision >= $y) && ($x - $precision <= $y);
  315. }
  316. /**
  317. * Get the settings for a specific formatName
  318. *
  319. * @param string $formatName (EUR, ...)
  320. * @return array $currencySettings or null on failure
  321. */
  322. public static function getFormat($formatName) {
  323. if (!isset(self::$_currencies[$formatName])) {
  324. return null;
  325. }
  326. return self::$_currencies[$formatName];
  327. }
  328. }