Number.php 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. namespace Tools\Utility;
  3. use Cake\I18n\Number as CakeNumber;
  4. /**
  5. * Extend CakeNumber with a few important improvements:
  6. * - config setting for format()
  7. * - spacer char for currency (initially from https://github.com/cakephp/cakephp/pull/1148)
  8. * - signed values possible
  9. */
  10. class Number extends CakeNumber {
  11. /**
  12. * @var array
  13. */
  14. protected static $_currencies = [];
  15. /**
  16. * @var string
  17. */
  18. protected static $_currency = 'EUR';
  19. /**
  20. * @var string
  21. */
  22. protected static $_symbolRight = '€';
  23. /**
  24. * @var string
  25. */
  26. protected static $_symbolLeft = '';
  27. /**
  28. * @var string
  29. */
  30. protected static $_decimals = ',';
  31. /**
  32. * @var string
  33. */
  34. protected static $_thousands = '.';
  35. /**
  36. * Convenience method to display the default currency
  37. *
  38. * @param float $value
  39. * @param array $formatOptions
  40. *
  41. * @return string
  42. */
  43. public static function money($value, array $formatOptions = []): string {
  44. return static::currency($value, null, $formatOptions);
  45. }
  46. /**
  47. * Format numeric values
  48. * should not be used for currencies
  49. * //TODO: automize per localeconv() ?
  50. *
  51. * @param float $number
  52. * @param array $formatOptions Format options: currency=true/false, ... (leave empty for no special treatment)
  53. * @return string
  54. */
  55. public static function _format($number, array $formatOptions = []) {
  56. if (!is_numeric($number)) {
  57. $default = '---';
  58. if (isset($formatOptions['default'])) {
  59. $default = $formatOptions['default'];
  60. }
  61. return $default;
  62. }
  63. $defaults = ['before' => '', 'after' => '', 'places' => 2, 'thousands' => static::$_thousands, 'decimals' => static::$_decimals, 'escape' => false];
  64. $options = $formatOptions + $defaults;
  65. if (!empty($options['currency'])) {
  66. if (!empty(static::$_symbolRight)) {
  67. $options['after'] = ' ' . static::$_symbolRight;
  68. } elseif (!empty(static::$_symbolLeft)) {
  69. $options['before'] = static::$_symbolLeft . ' ';
  70. }
  71. }
  72. /*
  73. if ($spacer !== false) {
  74. $spacer = ($spacer === true) ? ' ' : $spacer;
  75. if ((string)$before !== '') {
  76. $before .= $spacer;
  77. }
  78. if ((string)$after !== '') {
  79. $after = $spacer . $after;
  80. }
  81. }
  82. */
  83. if ($options['places'] < 0) {
  84. $number = round($number, $options['places']);
  85. }
  86. $sign = '';
  87. if ($number > 0 && !empty($options['signed'])) {
  88. $sign = '+';
  89. }
  90. if (isset($options['signed'])) {
  91. unset($options['signed']);
  92. }
  93. return $sign . parent::format($number, $options);
  94. }
  95. /**
  96. * Format
  97. *
  98. * Additional options
  99. * - signed
  100. * - positive
  101. *
  102. * @param float $number
  103. * @param array<string, mixed> $options
  104. * @return string
  105. */
  106. public static function format($number, array $options = []): string {
  107. $defaults = [
  108. 'positive' => '+', 'signed' => false,
  109. ];
  110. $options += $defaults;
  111. $sign = '';
  112. if ($number > 0 && !empty($options['signed'])) {
  113. $sign = '+';
  114. }
  115. if (isset($options['signed'])) {
  116. unset($options['signed']);
  117. }
  118. return $sign . parent::format($number, $options);
  119. }
  120. /**
  121. * Overwrite to allow
  122. *
  123. * - signed: true/false
  124. *
  125. * @param string|float|int $value
  126. * @param string|null $currency
  127. * @param array<string, mixed> $options
  128. *
  129. * @return string
  130. */
  131. public static function currency(string|float|int $value, ?string $currency = null, array $options = []): string {
  132. $defaults = [
  133. 'positive' => '+', 'signed' => false,
  134. ];
  135. $options += $defaults;
  136. $sign = '';
  137. if ($value > 0 && !empty($options['signed'])) {
  138. $sign = $options['positive'];
  139. }
  140. return $sign . parent::currency($value, $currency, $options);
  141. }
  142. /**
  143. * Get the rounded average.
  144. *
  145. * @param array $values Values: int or float values
  146. * @param int $precision
  147. *
  148. * @return float Average
  149. */
  150. public static function average(array $values, int $precision = 0): float {
  151. if (!$values) {
  152. return 0.0;
  153. }
  154. return round(array_sum($values) / count($values), $precision);
  155. }
  156. /**
  157. * Round value.
  158. *
  159. * @param float $number
  160. * @param float $increment
  161. * @return float result
  162. */
  163. public static function roundTo($number, $increment = 1.0) {
  164. $precision = static::getDecimalPlaces($increment);
  165. $res = round($number, $precision);
  166. if ($precision <= 0) {
  167. $res = (int)$res;
  168. }
  169. return $res;
  170. }
  171. /**
  172. * Round value up.
  173. *
  174. * @param float $number
  175. * @param int $increment
  176. *
  177. * @return float|int Result
  178. */
  179. public static function roundUpTo($number, int $increment = 1) {
  180. return ceil($number / $increment) * $increment;
  181. }
  182. /**
  183. * Round value down.
  184. *
  185. * @param float $number
  186. * @param int $increment
  187. *
  188. * @return float result
  189. */
  190. public static function roundDownTo($number, int $increment = 1) {
  191. return floor($number / $increment) * $increment;
  192. }
  193. /**
  194. * Get decimal places
  195. *
  196. * @param float $number
  197. * @return int decimalPlaces
  198. */
  199. public static function getDecimalPlaces($number): int {
  200. $decimalPlaces = 0;
  201. while ($number > 1 && $number != 0) {
  202. $number /= 10;
  203. $decimalPlaces -= 1;
  204. }
  205. while ($number < 1 && $number != 0) {
  206. $number *= 10;
  207. $decimalPlaces += 1;
  208. }
  209. return $decimalPlaces;
  210. }
  211. /**
  212. * Can compare two float values
  213. *
  214. * @link http://php.net/manual/en/language.types.float.php
  215. * @param float $x
  216. * @param float $y
  217. * @param float $precision
  218. * @return bool
  219. */
  220. public static function isFloatEqual($x, $y, $precision = 0.0000001): bool {
  221. return ($x + $precision >= $y) && ($x - $precision <= $y);
  222. }
  223. /**
  224. * Get the settings for a specific formatName
  225. *
  226. * @param string $formatName (EUR, ...)
  227. *
  228. * @return array currencySettings
  229. */
  230. public static function getFormat(string $formatName): array {
  231. if (!isset(static::$_currencies[$formatName])) {
  232. return [];
  233. }
  234. return static::$_currencies[$formatName];
  235. }
  236. }