Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core;
  18  
  19  use calc_formula;
  20  
  21  defined('MOODLE_INTERNAL') || die();
  22  
  23  global $CFG;
  24  require_once($CFG->libdir . '/mathslib.php');
  25  
  26  /**
  27   * Unit tests of mathslib wrapper and underlying EvalMath library.
  28   *
  29   * @package    core
  30   * @category   phpunit
  31   * @copyright  2007 Petr Skoda {@link http://skodak.org}
  32   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  33   */
  34  class mathslib_test extends \basic_testcase {
  35  
  36      /**
  37       * Tests the basic formula evaluation.
  38       */
  39      public function test__basic() {
  40          $formula = new calc_formula('=1+2');
  41          $res = $formula->evaluate();
  42          $this->assertSame($res, 3, '3+1 is: %s');
  43      }
  44  
  45      /**
  46       * Tests the formula params.
  47       */
  48      public function test__params() {
  49          $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30));
  50          $res = $formula->evaluate();
  51          $this->assertSame(60, $res, '10+20+30 is: %s');
  52      }
  53  
  54      /**
  55       * Tests the changed params.
  56       */
  57      public function test__changing_params() {
  58          $formula = new calc_formula('=a+b+c', array('a'=>10, 'b'=>20, 'c'=>30));
  59          $res = $formula->evaluate();
  60          $this->assertSame(60, $res, '10+20+30 is: %s');
  61          $formula->set_params(array('a'=>1, 'b'=>2, 'c'=>3));
  62          $res = $formula->evaluate();
  63          $this->assertSame(6, $res, 'changed params 1+2+3 is: %s');
  64      }
  65  
  66      /**
  67       * Tests the spreadsheet emulation function in formula.
  68       */
  69      public function test__calc_function() {
  70          $formula = new calc_formula('=sum(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
  71          $res = $formula->evaluate();
  72          $this->assertSame(60, $res, 'sum(a, b, c) is: %s');
  73      }
  74  
  75      public function test_other_functions() {
  76          $formula = new calc_formula('=average(1,2,3)');
  77          $this->assertSame(2, $formula->evaluate());
  78  
  79          $formula = new calc_formula('=mod(10,3)');
  80          $this->assertSame(1, $formula->evaluate());
  81  
  82          $formula = new calc_formula('=power(2,3)');
  83          $this->assertSame(8, $formula->evaluate());
  84      }
  85  
  86      public function test_conditional_functions() {
  87          // Test ifthenelse.
  88          $formula = new calc_formula('=ifthenelse(1,2,3)');
  89          $this->assertSame(2, (int)$formula->evaluate());
  90  
  91          $formula = new calc_formula('=ifthenelse(0,2,3)');
  92          $this->assertSame(3, (int) $formula->evaluate());
  93  
  94          $formula = new calc_formula('=ifthenelse(2<3,2,3)');
  95          $this->assertSame(2, (int) $formula->evaluate());
  96  
  97          // Test synonym if.
  98          $formula = new calc_formula('=if(1,2,3)');
  99          $this->assertSame(2, (int)$formula->evaluate());
 100  
 101          $formula = new calc_formula('=if(0,2,3)');
 102          $this->assertSame(3, (int) $formula->evaluate());
 103  
 104          $formula = new calc_formula('=if(2<3,2,3)');
 105          $this->assertSame(2, (int) $formula->evaluate());
 106  
 107          // Test cond_and.
 108          $formula = new calc_formula('=cond_and(1,1,1)');
 109          $this->assertSame(1, (int)$formula->evaluate());
 110  
 111          $formula = new calc_formula('=cond_and(1,1,0)');
 112          $this->assertSame(0, (int) $formula->evaluate());
 113  
 114          $formula = new calc_formula('=cond_and(0,0,0)');
 115          $this->assertSame(0, (int) $formula->evaluate());
 116  
 117          // Test synonym and.
 118          $formula = new calc_formula('=and(1,1,1)');
 119          $this->assertSame(1, (int)$formula->evaluate());
 120  
 121          $formula = new calc_formula('=and(1,1,0)');
 122          $this->assertSame(0, (int) $formula->evaluate());
 123  
 124          $formula = new calc_formula('=and(0,0,0)');
 125          $this->assertSame(0, (int) $formula->evaluate());
 126  
 127          // Test cond_or.
 128          $formula = new calc_formula('=cond_or(1,1,1)');
 129          $this->assertSame(1, (int)$formula->evaluate());
 130  
 131          $formula = new calc_formula('=cond_or(1,1,0)');
 132          $this->assertSame(1, (int) $formula->evaluate());
 133  
 134          $formula = new calc_formula('=cond_or(0,0,0)');
 135          $this->assertSame(0, (int) $formula->evaluate());
 136  
 137          // Test synonym or.
 138          $formula = new calc_formula('=or(1,1,1)');
 139          $this->assertSame(1, (int)$formula->evaluate());
 140  
 141          $formula = new calc_formula('=or(1,1,0)');
 142          $this->assertSame(1, (int) $formula->evaluate());
 143  
 144          $formula = new calc_formula('=or(0,0,0)');
 145          $this->assertSame(0, (int) $formula->evaluate());
 146      }
 147  
 148      public function test_conditional_operators() {
 149          $formula = new calc_formula('=2==2');
 150          $this->assertSame(1, $formula->evaluate());
 151  
 152          $formula = new calc_formula('=2>3');
 153          $this->assertSame(0, $formula->evaluate());
 154          $formula = new calc_formula('=2<3');
 155          $this->assertSame(1, $formula->evaluate());
 156  
 157          $formula = new calc_formula('=(2<=3)');
 158          $this->assertSame(1, $formula->evaluate());
 159  
 160          $formula = new calc_formula('=(2<=3)*10');
 161          $this->assertSame(10, $formula->evaluate());
 162  
 163          $formula = new calc_formula('=(2>=3)*10');
 164          $this->assertSame(0, $formula->evaluate());
 165          $formula = new calc_formula('=2<3*10');
 166          $this->assertSame(10, $formula->evaluate());
 167      }
 168      /**
 169       * Tests the min and max functions.
 170       */
 171      public function test__minmax_function() {
 172          $formula = new calc_formula('=min(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
 173          $res = $formula->evaluate();
 174          $this->assertSame(10, $res, 'minimum is: %s');
 175          $formula = new calc_formula('=max(a, b, c)', array('a'=>10, 'b'=>20, 'c'=>30));
 176          $res = $formula->evaluate();
 177          $this->assertSame(30, $res, 'maximum is: %s');
 178      }
 179  
 180      /**
 181       * Tests special chars.
 182       */
 183      public function test__specialchars() {
 184          $formula = new calc_formula('=gi1 + gi2 + gi11', array('gi1'=>10, 'gi2'=>20, 'gi11'=>30));
 185          $res = $formula->evaluate();
 186          $this->assertSame(60, $res, 'sum is: %s');
 187      }
 188  
 189      /**
 190       * Tests some slightly more complex expressions.
 191       */
 192      public function test__more_complex_expressions() {
 193          $formula = new calc_formula('=pi() + a', array('a'=>10));
 194          $res = $formula->evaluate();
 195          $this->assertSame(pi()+10, $res);
 196          $formula = new calc_formula('=pi()^a', array('a'=>10));
 197          $res = $formula->evaluate();
 198          $this->assertSame(pow(pi(), 10), $res);
 199          $formula = new calc_formula('=-8*(5/2)^2*(1-sqrt(4))-8');
 200          $res = $formula->evaluate();
 201          $this->assertSame(-8*pow((5/2), 2)*(1-sqrt(4))-8, $res);
 202      }
 203  
 204      /**
 205       * Tests some slightly more complex expressions.
 206       */
 207      public function test__error_handling() {
 208          $formula = new calc_formula('=pi( + a', array('a'=>10));
 209          $res = $formula->evaluate();
 210          $this->assertFalse($res);
 211          $this->assertSame(get_string('unexpectedoperator', 'mathslib', '+'), $formula->get_error());
 212  
 213          $formula = new calc_formula('=pi(');
 214          $res = $formula->evaluate();
 215          $this->assertSame($res, false);
 216          $this->assertSame(get_string('expectingaclosingbracket', 'mathslib'), $formula->get_error());
 217  
 218          $formula = new calc_formula('=pi()^');
 219          $res = $formula->evaluate();
 220          $this->assertSame($res, false);
 221          $this->assertSame(get_string('operatorlacksoperand', 'mathslib', '^'), $formula->get_error());
 222  
 223      }
 224  
 225      public function test_rounding_function() {
 226          // Rounding to the default number of decimal places.
 227          // The default == 0.
 228  
 229          $formula = new calc_formula('=round(2.5)');
 230          $this->assertSame(3.0, $formula->evaluate());
 231  
 232          $formula = new calc_formula('=round(1.5)');
 233          $this->assertSame(2.0, $formula->evaluate());
 234  
 235          $formula = new calc_formula('=round(-1.49)');
 236          $this->assertSame(-1.0, $formula->evaluate());
 237  
 238          $formula = new calc_formula('=round(-2.49)');
 239          $this->assertSame(-2.0, $formula->evaluate());
 240  
 241          $formula = new calc_formula('=round(-1.5)');
 242          $this->assertSame(-2.0, $formula->evaluate());
 243  
 244          $formula = new calc_formula('=round(-2.5)');
 245          $this->assertSame(-3.0, $formula->evaluate());
 246  
 247          $formula = new calc_formula('=ceil(2.5)');
 248          $this->assertSame(3.0, $formula->evaluate());
 249  
 250          $formula = new calc_formula('=ceil(1.5)');
 251          $this->assertSame(2.0, $formula->evaluate());
 252  
 253          $formula = new calc_formula('=ceil(-1.49)');
 254          $this->assertSame(-1.0, $formula->evaluate());
 255  
 256          $formula = new calc_formula('=ceil(-2.49)');
 257          $this->assertSame(-2.0, $formula->evaluate());
 258  
 259          $formula = new calc_formula('=ceil(-1.5)');
 260          $this->assertSame(-1.0, $formula->evaluate());
 261  
 262          $formula = new calc_formula('=ceil(-2.5)');
 263          $this->assertSame(-2.0, $formula->evaluate());
 264  
 265          $formula = new calc_formula('=floor(2.5)');
 266          $this->assertSame(2.0, $formula->evaluate());
 267  
 268          $formula = new calc_formula('=floor(1.5)');
 269          $this->assertSame(1.0, $formula->evaluate());
 270  
 271          $formula = new calc_formula('=floor(-1.49)');
 272          $this->assertSame(-2.0, $formula->evaluate());
 273  
 274          $formula = new calc_formula('=floor(-2.49)');
 275          $this->assertSame(-3.0, $formula->evaluate());
 276  
 277          $formula = new calc_formula('=floor(-1.5)');
 278          $this->assertSame(-2.0, $formula->evaluate());
 279  
 280          $formula = new calc_formula('=floor(-2.5)');
 281          $this->assertSame(-3.0, $formula->evaluate());
 282  
 283          // Rounding to an explicit number of decimal places.
 284  
 285          $formula = new calc_formula('=round(2.5, 1)');
 286          $this->assertSame(2.5, $formula->evaluate());
 287  
 288          $formula = new calc_formula('=round(2.5, 0)');
 289          $this->assertSame(3.0, $formula->evaluate());
 290  
 291          $formula = new calc_formula('=round(1.2345, 2)');
 292          $this->assertSame(1.23, $formula->evaluate());
 293  
 294          $formula = new calc_formula('=round(123.456, -1)');
 295          $this->assertSame(120.0, $formula->evaluate());
 296      }
 297  
 298      public function test_scientific_notation() {
 299          $formula = new calc_formula('=10e10');
 300          $this->assertEqualsWithDelta(1e11, $formula->evaluate(), 1e11 * 1e-15);
 301  
 302          $formula = new calc_formula('=10e-10');
 303          $this->assertEqualsWithDelta(1e-9, $formula->evaluate(), 1e11 * 1e-15);
 304  
 305          $formula = new calc_formula('=10e+10');
 306          $this->assertEqualsWithDelta(1e11, $formula->evaluate(), 1e11 * 1e-15);
 307  
 308          $formula = new calc_formula('=10e10*5');
 309          $this->assertEqualsWithDelta(5e11, $formula->evaluate(), 1e11 * 1e-15);
 310  
 311          $formula = new calc_formula('=10e10^2');
 312          $this->assertEqualsWithDelta(1e22, $formula->evaluate(), 1e22 * 1e-15);
 313      }
 314  
 315      public function test_rand_float() {
 316          $formula = new calc_formula('=rand_float()');
 317          $result = $formula->evaluate();
 318          $this->assertTrue(is_float($result));
 319      }
 320  
 321      /**
 322       * Tests the modulo operator.
 323       *
 324       * @covers calc_formula::evaluate
 325       * @dataProvider moduloOperatorData
 326       *
 327       * @param string $formula
 328       * @param array $values
 329       * @param int|float $expectedResult
 330       */
 331      public function shouldSupportModuloOperator($formula, $values, $expectedResult)
 332      {
 333          $formula = new calc_formula($formula);
 334          $formula->set_params($values);
 335          $this->assertEquals($expectedResult, $formula->evaluate());
 336      }
 337  
 338      /**
 339       * Data provider for shouldSupportModuloOperator
 340       *
 341       * @return array
 342       */
 343      public function moduloOperatorData() {
 344          return array(
 345              array(
 346                  '=a%b', // 9%3 => 0
 347                  array('a' => 9, 'b' => 3),
 348                  0
 349              ),
 350              array(
 351                  '=a%b', // 10%3 => 1
 352                  array('a' => 10, 'b' => 3),
 353                  1
 354              ),
 355              array(
 356                  '=10-a%(b+c*d)', // 10-10%(7-2*2) => 9
 357                  array('a' => '10', 'b' => 7, 'c' => -2, 'd' => 2),
 358                  9
 359              )
 360          );
 361      }
 362  
 363      /**
 364       * Tests the double minus as plus.
 365       *
 366       * @covers calc_formula::evaluate
 367       * @dataProvider doubleMinusData
 368       *
 369       * @param string $formula
 370       * @param array $values
 371       * @param int|float $expectedResult
 372       */
 373      public function shouldConsiderDoubleMinusAsPlus($formula, $values, $expectedResult)
 374      {
 375          $formula = new calc_formula($formula);
 376          $formula->set_params($values);
 377          $this->assertEquals($expectedResult, $formula->evaluate());
 378      }
 379  
 380      /**
 381       * Data provider for shouldConsiderDoubleMinusAsPlus
 382       *
 383       * @return array
 384       */
 385      public function doubleMinusData() {
 386          return array(
 387              array(
 388                  '=a+b*c--d', // 1+2*3--4 => 1+6+4 => 11
 389                  array(
 390                      'a' => 1,
 391                      'b' => 2,
 392                      'c' => 3,
 393                      'd' => 4
 394                  ),
 395                  11
 396              ),
 397              array(
 398                  '=a+b*c--d', // 1+2*3---4 => 1+6-4 => 3
 399                  array(
 400                      'a' => 1,
 401                      'b' => 2,
 402                      'c' => 3,
 403                      'd' => -4
 404                  ),
 405                  3
 406              )
 407          );
 408      }
 409  }