MDL-33911 lib/evalmath: fix strict syntax notices
[moodle.git] / lib / evalmath / evalmath.class.php
CommitLineData
aaa6ac6f 1<?php
2
3/*
4================================================================================
5
6EvalMath - PHP Class to safely evaluate math expressions
7Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
8
9================================================================================
10
11NAME
12 EvalMath - safely evaluate math expressions
6f5e0852 13
aaa6ac6f 14SYNOPSIS
15 <?
16 include('evalmath.class.php');
17 $m = new EvalMath;
18 // basic evaluation:
19 $result = $m->evaluate('2+2');
20 // supports: order of operation; parentheses; negation; built-in functions
21 $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
22 // create your own variables
23 $m->evaluate('a = e^(ln(pi))');
24 // or functions
25 $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
26 // and then use them
27 $result = $m->evaluate('3*f(42,a)');
28 ?>
6f5e0852 29
aaa6ac6f 30DESCRIPTION
6f5e0852 31 Use the EvalMath class when you want to evaluate mathematical expressions
aaa6ac6f 32 from untrusted sources. You can define your own variables and functions,
33 which are stored in the object. Try it, it's fun!
34
35METHODS
36 $m->evalute($expr)
37 Evaluates the expression and returns the result. If an error occurs,
38 prints a warning and returns false. If $expr is a function assignment,
39 returns true on success.
6f5e0852 40
aaa6ac6f 41 $m->e($expr)
42 A synonym for $m->evaluate().
6f5e0852 43
aaa6ac6f 44 $m->vars()
45 Returns an associative array of all user-defined variables and values.
6f5e0852 46
aaa6ac6f 47 $m->funcs()
48 Returns an array of all user-defined functions.
49
50PARAMETERS
51 $m->suppress_errors
52 Set to true to turn off warnings when evaluating expressions
53
54 $m->last_error
55 If the last evaluation failed, contains a string describing the error.
56 (Useful when suppress_errors is on).
57
58AUTHOR INFORMATION
59 Copyright 2005, Miles Kaufmann.
60
61LICENSE
62 Redistribution and use in source and binary forms, with or without
63 modification, are permitted provided that the following conditions are
64 met:
6f5e0852 65
aaa6ac6f 66 1 Redistributions of source code must retain the above copyright
67 notice, this list of conditions and the following disclaimer.
68 2. Redistributions in binary form must reproduce the above copyright
69 notice, this list of conditions and the following disclaimer in the
70 documentation and/or other materials provided with the distribution.
71 3. The name of the author may not be used to endorse or promote
72 products derived from this software without specific prior written
73 permission.
6f5e0852 74
aaa6ac6f 75 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
76 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
77 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
78 DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
79 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
80 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
81 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
82 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
83 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
84 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
85 POSSIBILITY OF SUCH DAMAGE.
86
87*/
88
89/**
90 * This class was heavily modified in order to get usefull spreadsheet emulation ;-)
91 * skodak
6f5e0852 92 *
aaa6ac6f 93 */
94
95class EvalMath {
96
950dc914
JP
97 /** @var string Pattern used for a valid function or variable name. Note, var and func names are case insensitive.*/
98 private static $namepat = '[a-z][a-z0-9_]*';
99
aaa6ac6f 100 var $suppress_errors = false;
101 var $last_error = null;
6f5e0852 102
aaa6ac6f 103 var $v = array(); // variables (and constants)
104 var $f = array(); // user-defined functions
105 var $vb = array(); // constants
106 var $fb = array( // built-in functions
107 'sin','sinh','arcsin','asin','arcsinh','asinh',
108 'cos','cosh','arccos','acos','arccosh','acosh',
109 'tan','tanh','arctan','atan','arctanh','atanh',
eb9f06f9 110 'sqrt','abs','ln','log','exp','floor','ceil');
aaa6ac6f 111
112 var $fc = array( // calc functions emulation
113 'average'=>array(-1), 'max'=>array(-1), 'min'=>array(-1),
114 'mod'=>array(2), 'pi'=>array(0), 'power'=>array(2),
950dc914
JP
115 'round'=>array(1, 2), 'sum'=>array(-1), 'rand_int'=>array(2),
116 'rand_float'=>array(0));
6f5e0852 117
94a67b39 118 var $allowimplicitmultiplication;
119
120 function EvalMath($allowconstants = false, $allowimplicitmultiplication = false) {
121 if ($allowconstants){
122 $this->v['pi'] = pi();
123 $this->v['e'] = exp(1);
124 }
125 $this->allowimplicitmultiplication = $allowimplicitmultiplication;
aaa6ac6f 126 }
6f5e0852 127
aaa6ac6f 128 function e($expr) {
129 return $this->evaluate($expr);
130 }
6f5e0852 131
aaa6ac6f 132 function evaluate($expr) {
133 $this->last_error = null;
134 $expr = trim($expr);
135 if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
136 //===============
137 // is it a variable assignment?
e2beee58 138 if (preg_match('/^\s*('.self::$namepat.')\s*=\s*(.+)$/', $expr, $matches)) {
aaa6ac6f 139 if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
13264f35 140 return $this->trigger(get_string('cannotassigntoconstant', 'mathslib', $matches[1]));
aaa6ac6f 141 }
142 if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
143 $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
144 return $this->v[$matches[1]]; // and return the resulting value
145 //===============
146 // is it a function assignment?
e2beee58 147 } elseif (preg_match('/^\s*('.self::$namepat.')\s*\(\s*('.self::$namepat.'(?:\s*,\s*'.self::$namepat.')*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
aaa6ac6f 148 $fnn = $matches[1]; // get the function name
149 if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
13264f35 150 return $this->trigger(get_string('cannotredefinebuiltinfunction', 'mathslib', $matches[1]));
aaa6ac6f 151 }
152 $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
153 if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
154 for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
155 $token = $stack[$i];
e2beee58 156 if (preg_match('/^'.self::$namepat.'$/', $token) and !in_array($token, $args)) {
aaa6ac6f 157 if (array_key_exists($token, $this->v)) {
158 $stack[$i] = $this->v[$token];
159 } else {
13264f35 160 return $this->trigger(get_string('undefinedvariableinfunctiondefinition', 'mathslib', $token));
aaa6ac6f 161 }
162 }
163 }
164 $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
165 return true;
166 //===============
167 } else {
168 return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
169 }
170 }
6f5e0852 171
aaa6ac6f 172 function vars() {
173 return $this->v;
174 }
6f5e0852 175
aaa6ac6f 176 function funcs() {
177 $output = array();
178 foreach ($this->f as $fnn=>$dat)
179 $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
180 return $output;
181 }
182
6efaec1e 183 /**
184 * @param string $name
185 * @return boolean Is this a valid var or function name?
186 */
187 public static function is_valid_var_or_func_name($name){
188 return preg_match('/'.self::$namepat.'$/iA', $name);
189 }
190
aaa6ac6f 191 //===================== HERE BE INTERNAL METHODS ====================\\
192
193 // Convert infix to postfix notation
194 function nfx($expr) {
6f5e0852 195
aaa6ac6f 196 $index = 0;
197 $stack = new EvalMathStack;
198 $output = array(); // postfix form of expression, to be passed to pfx()
199 $expr = trim(strtolower($expr));
6f5e0852 200
aaa6ac6f 201 $ops = array('+', '-', '*', '/', '^', '_');
6f5e0852 202 $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
aaa6ac6f 203 $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
6f5e0852 204
aaa6ac6f 205 $expecting_op = false; // we use this in syntax-checking the expression
206 // and determining when a - is a negation
6f5e0852 207
aaa6ac6f 208 if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
13264f35 209 return $this->trigger(get_string('illegalcharactergeneral', 'mathslib', $matches[0]));
aaa6ac6f 210 }
6f5e0852 211
aaa6ac6f 212 while(1) { // 1 Infinite Loop ;)
213 $op = substr($expr, $index, 1); // get the first character at the current index
214 // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
9085134e 215 $ex = preg_match('/^('.self::$namepat.'\(?|\d+(?:\.\d*)?(?:(e[+-]?)\d*)?|\.\d+|\()/', substr($expr, $index), $match);
aaa6ac6f 216 //===============
217 if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
218 $stack->push('_'); // put a negation on the stack
219 $index++;
6f5e0852 220 } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
13264f35 221 return $this->trigger(get_string('illegalcharacterunderscore', 'mathslib')); // but not in the input expression
aaa6ac6f 222 //===============
223 } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
224 if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
94a67b39 225 if (!$this->allowimplicitmultiplication){
13264f35 226 return $this->trigger(get_string('implicitmultiplicationnotallowed', 'mathslib'));
94a67b39 227 } else {// it's an implicit multiplication
228 $op = '*';
229 $index--;
230 }
aaa6ac6f 231 }
232 // heart of the algorithm:
233 while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
234 $output[] = $stack->pop(); // pop stuff off the stack into the output
235 }
236 // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
237 $stack->push($op); // finally put OUR operator onto the stack
238 $index++;
239 $expecting_op = false;
240 //===============
241 } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
242 while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
13264f35 243 if (is_null($o2)) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
aaa6ac6f 244 else $output[] = $o2;
245 }
e2beee58 246 if (preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches)) { // did we just close a function?
aaa6ac6f 247 $fnn = $matches[1]; // get the function name
248 $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
249 $fn = $stack->pop();
250 $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>$arg_count); // send function to output
251 if (in_array($fnn, $this->fb)) { // check the argument count
13264f35 252 if($arg_count > 1) {
253 $a= new stdClass();
254 $a->expected = 1;
255 $a->given = $arg_count;
256 return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
257 }
aaa6ac6f 258 } elseif (array_key_exists($fnn, $this->fc)) {
259 $counts = $this->fc[$fnn];
260 if (in_array(-1, $counts) and $arg_count > 0) {}
13264f35 261 elseif (!in_array($arg_count, $counts)) {
262 $a= new stdClass();
263 $a->expected = implode('/',$this->fc[$fnn]);
264 $a->given = $arg_count;
265 return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
266 }
aaa6ac6f 267 } elseif (array_key_exists($fnn, $this->f)) {
13264f35 268 if ($arg_count != count($this->f[$fnn]['args'])) {
269 $a= new stdClass();
270 $a->expected = count($this->f[$fnn]['args']);
271 $a->given = $arg_count;
272 return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
273 }
aaa6ac6f 274 } else { // did we somehow push a non-function on the stack? this should never happen
13264f35 275 return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 276 }
277 }
278 $index++;
279 //===============
280 } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
6f5e0852 281 while (($o2 = $stack->pop()) != '(') {
13264f35 282 if (is_null($o2)) return $this->trigger(get_string('unexpectedcomma', 'mathslib')); // oops, never had a (
aaa6ac6f 283 else $output[] = $o2; // pop the argument expression stuff and push onto the output
284 }
285 // make sure there was a function
e2beee58 286 if (!preg_match('/^('.self::$namepat.')\($/', $stack->last(2), $matches))
13264f35 287 return $this->trigger(get_string('unexpectedcomma', 'mathslib'));
aaa6ac6f 288 $stack->push($stack->pop()+1); // increment the argument count
289 $stack->push('('); // put the ( back on, we'll need to pop back to it again
290 $index++;
291 $expecting_op = false;
292 //===============
293 } elseif ($op == '(' and !$expecting_op) {
294 $stack->push('('); // that was easy
295 $index++;
296 $allow_neg = true;
297 //===============
298 } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
299 $expecting_op = true;
300 $val = $match[1];
e2beee58 301 if (preg_match('/^('.self::$namepat.')\($/', $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
aaa6ac6f 302 if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f) or array_key_exists($matches[1], $this->fc)) { // it's a func
303 $stack->push($val);
304 $stack->push(1);
305 $stack->push('(');
306 $expecting_op = false;
307 } else { // it's a var w/ implicit multiplication
308 $val = $matches[1];
309 $output[] = $val;
310 }
311 } else { // it's a plain old var or num
312 $output[] = $val;
313 }
314 $index += strlen($val);
315 //===============
316 } elseif ($op == ')') {
317 //it could be only custom function with no params or general error
13264f35 318 if ($stack->last() != '(' or $stack->last(2) != 1) return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
e2beee58 319 if (preg_match('/^('.self::$namepat.')\($/', $stack->last(3), $matches)) { // did we just close a function?
aaa6ac6f 320 $stack->pop();// (
321 $stack->pop();// 1
322 $fn = $stack->pop();
323 $fnn = $matches[1]; // get the function name
324 $counts = $this->fc[$fnn];
13264f35 325 if (!in_array(0, $counts)){
326 $a= new stdClass();
327 $a->expected = $this->fc[$fnn];
950dc914 328 $a->given = 0;
13264f35 329 return $this->trigger(get_string('wrongnumberofarguments', 'mathslib', $a));
330 }
aaa6ac6f 331 $output[] = array('fn'=>$fn, 'fnn'=>$fnn, 'argcount'=>0); // send function to output
332 $index++;
c4f7eecf 333 $expecting_op = true;
aaa6ac6f 334 } else {
13264f35 335 return $this->trigger(get_string('unexpectedclosingbracket', 'mathslib'));
aaa6ac6f 336 }
337 //===============
338 } elseif (in_array($op, $ops) and !$expecting_op) { // miscellaneous error checking
13264f35 339 return $this->trigger(get_string('unexpectedoperator', 'mathslib', $op));
aaa6ac6f 340 } else { // I don't even want to know what you did to get here
13264f35 341 return $this->trigger(get_string('anunexpectederroroccured', 'mathslib'));
aaa6ac6f 342 }
343 if ($index == strlen($expr)) {
344 if (in_array($op, $ops)) { // did we end with an operator? bad.
13264f35 345 return $this->trigger(get_string('operatorlacksoperand', 'mathslib', $op));
aaa6ac6f 346 } else {
347 break;
348 }
349 }
6f5e0852 350 while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
aaa6ac6f 351 $index++; // into implicit multiplication if no operator is there)
352 }
6f5e0852
PS
353
354 }
aaa6ac6f 355 while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
13264f35 356 if ($op == '(') return $this->trigger(get_string('expectingaclosingbracket', 'mathslib')); // if there are (s on the stack, ()s were unbalanced
aaa6ac6f 357 $output[] = $op;
358 }
359 return $output;
360 }
361
362 // evaluate postfix notation
363 function pfx($tokens, $vars = array()) {
6f5e0852 364
aaa6ac6f 365 if ($tokens == false) return false;
6f5e0852 366
aaa6ac6f 367 $stack = new EvalMathStack;
6f5e0852 368
aaa6ac6f 369 foreach ($tokens as $token) { // nice and easy
370
371 // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
372 if (is_array($token)) { // it's a function!
373 $fnn = $token['fnn'];
374 $count = $token['argcount'];
375 if (in_array($fnn, $this->fb)) { // built-in function:
13264f35 376 if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 377 $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
378 if ($fnn == 'ln') $fnn = 'log';
379 eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
380 } elseif (array_key_exists($fnn, $this->fc)) { // calc emulation function
381 // get args
382 $args = array();
383 for ($i = $count-1; $i >= 0; $i--) {
13264f35 384 if (is_null($args[] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 385 }
1e257a3a 386 $res = call_user_func_array(array('EvalMathFuncs', $fnn), array_reverse($args));
aaa6ac6f 387 if ($res === FALSE) {
13264f35 388 return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 389 }
390 $stack->push($res);
391 } elseif (array_key_exists($fnn, $this->f)) { // user function
392 // get args
393 $args = array();
394 for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
13264f35 395 if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 396 }
397 $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
398 }
399 // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
400 } elseif (in_array($token, array('+', '-', '*', '/', '^'), true)) {
13264f35 401 if (is_null($op2 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
402 if (is_null($op1 = $stack->pop())) return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 403 switch ($token) {
404 case '+':
405 $stack->push($op1+$op2); break;
406 case '-':
407 $stack->push($op1-$op2); break;
408 case '*':
409 $stack->push($op1*$op2); break;
410 case '/':
13264f35 411 if ($op2 == 0) return $this->trigger(get_string('divisionbyzero', 'mathslib'));
aaa6ac6f 412 $stack->push($op1/$op2); break;
413 case '^':
414 $stack->push(pow($op1, $op2)); break;
415 }
416 // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
417 } elseif ($token == "_") {
418 $stack->push(-1*$stack->pop());
419 // if the token is a number or variable, push it on the stack
420 } else {
421 if (is_numeric($token)) {
422 $stack->push($token);
423 } elseif (array_key_exists($token, $this->v)) {
424 $stack->push($this->v[$token]);
425 } elseif (array_key_exists($token, $vars)) {
426 $stack->push($vars[$token]);
427 } else {
13264f35 428 return $this->trigger(get_string('undefinedvariable', 'mathslib', $token));
aaa6ac6f 429 }
430 }
431 }
432 // when we're out of tokens, the stack should have a single element, the final result
13264f35 433 if ($stack->count != 1) return $this->trigger(get_string('internalerror', 'mathslib'));
aaa6ac6f 434 return $stack->pop();
435 }
6f5e0852 436
aaa6ac6f 437 // trigger an error, but nicely, if need be
438 function trigger($msg) {
439 $this->last_error = $msg;
440 if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
441 return false;
442 }
6efaec1e 443
aaa6ac6f 444}
445
446// for internal use
447class EvalMathStack {
448
449 var $stack = array();
450 var $count = 0;
6f5e0852 451
aaa6ac6f 452 function push($val) {
453 $this->stack[$this->count] = $val;
454 $this->count++;
455 }
6f5e0852 456
aaa6ac6f 457 function pop() {
458 if ($this->count > 0) {
459 $this->count--;
460 return $this->stack[$this->count];
461 }
462 return null;
463 }
6f5e0852 464
aaa6ac6f 465 function last($n=1) {
e5e2fc05 466 if ($this->count - $n >= 0) {
467 return $this->stack[$this->count-$n];
468 }
469 return null;
aaa6ac6f 470 }
471}
472
950dc914 473
c4f710a9 474// spreadsheet functions emulation
1e257a3a 475class EvalMathFuncs {
aaa6ac6f 476
1e257a3a
JP
477 static function average() {
478 $args = func_get_args();
479 return (call_user_func_array(array('self', 'sum'), $args) / count($args));
aaa6ac6f 480 }
481
1e257a3a
JP
482 static function max() {
483 $args = func_get_args();
aaa6ac6f 484 $res = array_pop($args);
485 foreach($args as $a) {
486 if ($res < $a) {
487 $res = $a;
488 }
489 }
490 return $res;
491 }
492
1e257a3a
JP
493 static function min() {
494 $args = func_get_args();
aaa6ac6f 495 $res = array_pop($args);
496 foreach($args as $a) {
497 if ($res > $a) {
498 $res = $a;
499 }
500 }
501 return $res;
502 }
1e257a3a
JP
503
504 static function mod($op1, $op2) {
505 return $op1 % $op2;
aaa6ac6f 506 }
1e257a3a
JP
507
508 static function pi() {
aaa6ac6f 509 return pi();
510 }
1e257a3a
JP
511
512 static function power($op1, $op2) {
513 return pow($op1, $op2);
aaa6ac6f 514 }
515
1e257a3a
JP
516 static function round($val, $precision = 0) {
517 return round($val, $precision);
aaa6ac6f 518 }
1e257a3a
JP
519
520 static function sum() {
521 $args = func_get_args();
aaa6ac6f 522 $res = 0;
523 foreach($args as $a) {
524 $res += $a;
525 }
526 return $res;
527 }
1e257a3a 528
950dc914
JP
529 protected static $randomseed = null;
530
531 static function set_random_seed($randomseed) {
532 self::$randomseed = $randomseed;
533 }
534
535 static function get_random_seed() {
536 if (is_null(self::$randomseed)){
537 return microtime();
538 } else {
539 return self::$randomseed;
540 }
541 }
542
1e257a3a 543 static function rand_int($min, $max){
950dc914
JP
544 if ($min >= $max) {
545 return false; //error
546 }
547 $noofchars = ceil(log($max + 1 - $min, '16'));
548 $md5string = md5(self::get_random_seed());
549 $stringoffset = 0;
550 do {
551 while (($stringoffset + $noofchars) > strlen($md5string)){
552 $md5string .= md5($md5string);
553 }
554 $randomno = hexdec(substr($md5string, $stringoffset, $noofchars));
555 $stringoffset += $noofchars;
556 } while (($min + $randomno) > $max);
557 return $min + $randomno;
558 }
1e257a3a 559
4efc3d40
TH
560 static function rand_float() {
561 $randomvalues = unpack('v', md5(self::get_random_seed(), true));
562 return array_shift($randomvalues) / 65536;
950dc914 563 }
950dc914 564}