RSI helpers  0.1
RSI helpers
Number.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Rsi;
4 
5 /**
6  * Helpers for numbers of all sorts.
7  */
8 class Number{
9 
10  /**
11  * Add a value to a non-decimal number.
12  * @param string $value Base value in base $base.
13  * @param int $add Decimal value to add to $value.
14  * @param int $base The base the $value is in (defaults to 10).
15  * @param bool $upper Convert the return value to uppercase (only for $base > 10). Auto-detect on null (defaults to true).
16  * @return string Value $value with $add added, in base $base.
17  */
18  public static function add($value,$add = 1,$base = null,$upper = null){
19  if($upper === null) $upper = ($base > 10) && (strtoupper($value) == $value);
20  if($base) $value = base_convert($value,$base,10);
21  $value += $add;
22  if($base) $value = base_convert($value,10,$base);
23  if($upper) $value = strtoupper($value);
24  return $value;
25  }
26  /**
27  * Returns the size in bytes for shorthand notations (e.g. 1k -> 1024).
28  * @see http://php.net/manual/en/faq.using.php#faq.using.shorthandbytes
29  * @param string $size Size in shorthand format.
30  * @return int Size in bytes.
31  */
32  public static function shorthandBytes($size){
33  $factor = 1;
34  switch(strtolower(substr($size,-1))){
35  case 'e': $factor <<= 10;
36  case 'p': $factor <<= 10;
37  case 't': $factor <<= 10;
38  case 'g': $factor <<= 10;
39  case 'm': $factor <<= 10;
40  case 'k': $factor <<= 10;
41  }
42  return $factor * (int)$size;
43  }
44  /**
45  * Formats a size in bytes to its shorthand format.
46  * @param int $size Size in bytes.
47  * @param int $decimals Number of decimals.
48  * @param string $dec_point Separator for the decimal point.
49  * @param string $thousands_sep Thousands separator.
50  * @param string $unit_sep Separator between number and unit.
51  * @return string
52  */
53  public static function formatBytes($size,$decimals = 1,$dec_point = '.',$thousands_sep = ',',$unit_sep = ' '){
54  $unit = 'b';
55  $units = ['kb','Mb','Gb','Tb'];
56  if($size < 1024) $decimals = 0;
57  else while(($size >= 1024) && $units){
58  $size = $size / 1024;
59  $unit = array_shift($units);
60  }
61  return number_format($size,$decimals,$dec_point,$thousands_sep) . $unit_sep . $unit;
62  }
63  /**
64  * Format a number as a Roman number.
65  * @param int $value Decimal value.
66  * @return string Roman representation.
67  */
68  public static function toRoman($value){
69  $result = '';
70  $numbers = ['M' => 1000,'CM' => 900,'D' => 500,'CD' => 400,'C' => 100,'XC' => 90,'L' => 50,'XL' => 40,'X' => 10,'IX' => 9,'V' => 5,'IV' => 4,'I' => 1];
71  while($value > 0) foreach($numbers as $roman => $number) if($value >= $number){
72  $value -= $number;
73  $result .= $roman;
74  break;
75  }
76  return $result;
77  }
78 
79  public static function celsiusToFahrenheit($value){
80  return $value * 1.8 + 32;
81  }
82 
83  public static function fahrenheitToCelsius($value){
84  return ($value - 32) / 1.8;
85  }
86  /**
87  * Split a value into different units.
88  * @param float $value Number to split.
89  * @param array $units Units to split into (key = name, value = count/unit).
90  * @param float $tolerance Stop splitting if the remainder is smaller than this amount.
91  * @return array Units (key = name, value = count), same sequence as units.
92  */
93  public static function units($value,$units,$tolerance = 0){
94  $result = $counts = [];
95  foreach(array_reverse(array_keys(\Rsi\Record::sort($units))) as $unit){
96  $count = $value / $units[$unit];
97  if($unit) $count = floor($count);
98  if($count || $counts) $counts[$unit] = $count;
99  if(($value -= $count * $units[$unit]) < $tolerance) break;
100  }
101  foreach($units as $unit => $count) if(array_key_exists($unit,$counts)) $result[$unit] = $counts[$unit];
102  return $result;
103  }
104  /**
105  * Turn a decimal number into a fraction.
106  * @param float $value
107  * @param float $tolerance Tolerance for result.
108  * @return array Array with numerator and denominator.
109  */
110  public static function fraction($value,$tolerance = 0.000001){
111  $num = $prev_den = 1;
112  $den = $prev_num = 0;
113  $sign = $value < 0 ? -1 : 1;
114  $frac = 1 / ($value = abs($value));
115  do{
116  $frac = 1 / $frac;
117  $rest = floor($frac);
118  $prev = $num; $num = $rest * $num + $prev_num; $prev_num = $prev;
119  $prev = $den; $den = $rest * $den + $prev_den; $prev_den = $prev;
120  $frac = $frac - $rest;
121  }
122  while(abs($value - $num / $den) > $value * $tolerance);
123  return [$sign * $num,$den];
124  }
125  /**
126  * Evaluate a mathamatical expression.
127  * @param string $expr Mathamatical expression.
128  * @return float Answer (throws Exception on error).
129  */
130  public static function evaluate($expr){
131  $number = '-?\\d+(?:\\.\\d+)?';
132  $operators = ['^','*','/','%','+','-','>','>=','<','<=','==','!='];
133  $functions = [
134  'decbin','dechex','decoct','deg2rad','rad2deg',
135  'abs','floor','ceil','sqrt','exp','ln','log','pi',
136  'sin','sinh','arcsin','asin','arcsinh','asinh',
137  'cos','cosh','arccos','acos','arccosh','acosh',
138  'tan','tanh','arctan','atan','arctanh','atanh'
139  ];
140  $array_functions = [
141  'min','max','count','sum','unique','filter','product'
142  ];
143  //separate minus operator from number
144  $expr = substr(preg_replace('/(?<![\\(' . preg_quote(implode($operators),'/') . '])\\-/','- ','^' . $expr),1);
145 
146  //fix dates
147  if(preg_match_all('/\'(\\d{4})- (\\d{1,2})- (\\d{1,2})/',$expr,$matches,PREG_SET_ORDER))
148  foreach($matches as list($full,$year,$month,$day))
149  $expr = str_replace($full,"'$year-" . Str::pad($month,2) . '-' . Str::pad($day,2),$expr);
150 
151  //evaluate groups and functions
152  $expr = str_replace('π','pi()',$expr);
153  while(preg_match('/(\\w*)\\(([^\\(\\)]*)\\)/',$expr,$match)){
154  list($full,$func_name,$params) = $match;
155  if($func_name){
156  $params = explode(',',$params);
157  $value = null;
158  if(in_array($func_name,$functions)) $value = call_user_func($func_name,$params[0] === '' ? null : self::evaluate($params[0]));
159  elseif(in_array($func_name,$array_functions)){
160  if(is_array($value = call_user_func(function_exists($array_name = 'array_' . $func_name) ? $array_name : $func_name,array_map(['self','evaluate'],$params))))
161  $value = implode(',',$value);
162  }
163  else switch($func_name){
164  case 'time':
165  if(($value = $params[0]) === '') $value = time();
166  elseif(substr($value = $params[0],0,1) == "'") $value = strtotime(substr($value,1,-1));
167  if($modifier = $params[1] ?? null) $value = strtotime($modifier,$value);
168  break;
169  case 'date':
170  if(!is_numeric($value = date($params[1] ?? 'Y-m-d',self::evaluate($params[0])))) return $value;
171  break;
172  case 'frac': $value = fmod(self::evaluate($params[0]),1); break;
173  case 'fracnum': $value = self::fraction(self::evaluate($params[0]),$params[1] ?? 0.000001)[0]; break;
174  case 'fracden': $value = self::fraction(self::evaluate($params[0]),$params[1] ?? 0.000001)[1]; break;
175  case 'round': $value = round(self::evaluate($params[0]),$params[1] ?? 0); break;
176  case 'rand': $value = rand(self::evaluate($params[0]),self::evaluate($params[1] ?? 0)); break;
177  case 'degc2f': $value = self::celsiusToFahrenheit(self::evaluate($params[0])); break;
178  case 'degf2c': $value = self::fahrenheitToCelsius(self::evaluate($params[0])); break;
179  case 'avg': return ($array = array_map(['self','evaluate'],$params)) ? array_sum($array) / count($array) : 0; break;
180  case 'first': $value = self::evaluate(array_shift($params)); break;
181  case 'last': $value = self::evaluate(array_pop($params)); break;
182  case 'exists': $value = array_search(self::evaluate(array_shift($params)),array_map(['self','evaluate'],$params)) === false ? 0 : 1; break;
183  case 'if': $value = self::evaluate($params[0]) ? self::evaluate($params[1] ?? 1) : self::evaluate($params[2] ?? 0); break;
184  default: throw new \Exception("Unknown function '$func_name'");
185  }
186  $expr = str_replace($full,$value,$expr);
187  }
188  else $expr = str_replace($full,self::evaluate($params),$expr); //group
189  }
190 
191  //evaluate operators
192  foreach($operators as $operator) while(preg_match('/(' . $number . ')\\s*' . preg_quote($operator,'/') . '\\s*(' . $number . ')/',$expr,$match)){
193  list($full,$a,$b) = $match;
194  $value = null;
195  switch($operator){
196  case '^': $value = pow($a,$b); break;
197  case '*': $value = $a * $b; break;
198  case '/': $value = $a / $b; break;
199  case '%': $value = $a % $b; break;
200  case '+': $value = $a + $b; break;
201  case '-': $value = $a - $b; break;
202  case '>': $value = intval($a > $b); break;
203  case '>=': $value = intval($a >= $b); break;
204  case '<': $value = intval($a < $b); break;
205  case '<=': $value = intval($a <= $b); break;
206  case '==': $value = intval($a == $b); break;
207  case '!=': $value = intval($a != $b); break;
208  default: throw new \Exception("Unknown operator '$operator'");
209  }
210  $expr = str_replace($full,$value,$expr);
211  }
212 
213  return $expr;
214  }
215 
216 }
static evaluate($expr)
Evaluate a mathamatical expression.
Definition: Number.php:130
static toRoman($value)
Format a number as a Roman number.
Definition: Number.php:68
static fahrenheitToCelsius($value)
Definition: Number.php:83
static shorthandBytes($size)
Returns the size in bytes for shorthand notations (e.g.
Definition: Number.php:32
static add($value, $add=1, $base=null, $upper=null)
Add a value to a non-decimal number.
Definition: Number.php:18
Helpers for numbers of all sorts.
Definition: Number.php:8
Definition: Color.php:3
static units($value, $units, $tolerance=0)
Split a value into different units.
Definition: Number.php:93
static celsiusToFahrenheit($value)
Definition: Number.php:79
static fraction($value, $tolerance=0.000001)
Turn a decimal number into a fraction.
Definition: Number.php:110
static formatBytes($size, $decimals=1, $dec_point='.', $thousands_sep=',', $unit_sep=' ')
Formats a size in bytes to its shorthand format.
Definition: Number.php:53