Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 29 additions & 13 deletions components/ILIAS/Math/classes/class.EvalMath.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
<?php

/**
* This file is part of ILIAS, a powerful learning management system
* published by ILIAS open source e-Learning e.V.
*
* ILIAS is licensed with the GPL-3.0,
* see https://www.gnu.org/licenses/gpl-3.0.en.html
* You should have received a copy of said license along with the
* source code, too.
*
* If this is not the case or you just want to try ILIAS, you'll find
* us at:
* https://www.ilias.de
* https://github.com/ILIAS-eLearning
*
*********************************************************************/

/*
================================================================================

Expand Down Expand Up @@ -118,8 +134,8 @@ public function evaluate(string $expr)
{
// convert exponential notation
$expr = preg_replace_callback(
"/(\\d{0,1})e(-{0,1}\\d+)/is",
fn ($hit): string => $hit[1] . ((strlen($hit[1])) ? '*' : '') . '10^(' . $hit[2] . ')',
"/(\\d{0,1})e([+-]{0,1}\\d+)/is",
fn($hit): string => $hit[1] . ((strlen($hit[1])) ? '*' : '') . '10^(' . ltrim($hit[2], '+') . ')',
$expr
);
// standard functionality
Expand All @@ -139,8 +155,8 @@ public function evaluate(string $expr)
} // get the result and make sure it's good
$this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
return $this->v[$matches[1]]; // and return the resulting value
//===============
// is it a function assignment?
//===============
// is it a function assignment?
} elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
$fnn = $matches[1]; // get the function name
if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
Expand Down Expand Up @@ -215,7 +231,7 @@ public function nfx($expr)
$index++;
} elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
return $this->trigger("illegal character '_'"); // but not in the input expression
//===============
//===============
} elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
$op = '*';
Expand All @@ -229,7 +245,7 @@ public function nfx($expr)
$stack->push($op); // finally put OUR operator onto the stack
$index++;
$expecting_op = false;
//===============
//===============
} elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
if (is_null($o2)) {
Expand All @@ -255,7 +271,7 @@ public function nfx($expr)
}
}
$index++;
//===============
//===============
} elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
while (($o2 = $stack->pop()) != '(') {
if (is_null($o2)) {
Expand All @@ -273,12 +289,12 @@ public function nfx($expr)
$stack->push('('); // put the ( back on, we'll need to pop back to it again
$index++;
$expecting_op = false;
//===============
//===============
} elseif ($op == '(' and !$expecting_op) {
$stack->push('('); // that was easy
$index++;
$allow_neg = true;
//===============
//===============
} elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
$expecting_op = true;
$val = $match[1];
Expand All @@ -296,7 +312,7 @@ public function nfx($expr)
$output[] = $val;
}
$index += strlen($val);
//===============
//===============
} elseif ($op == ')') { // miscellaneous error checking
return $this->trigger("unexpected ')'");
} elseif (in_array($op, $ops) and !$expecting_op) {
Expand Down Expand Up @@ -362,10 +378,10 @@ public function pfx($tokens, $vars = [])
$stack->push(ilMath::_pow($op1, $op2));
break;
}
// if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
// if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
} elseif ($token == "_") {
$stack->push(-1 * $stack->pop());
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
// if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
} elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
$fnn = $matches[1];
if (in_array($fnn, $this->fb)) { // built-in function:
Expand All @@ -390,7 +406,7 @@ public function pfx($tokens, $vars = [])
}
$stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
}
// if the token is a number or variable, push it on the stack
// if the token is a number or variable, push it on the stack
} else {
if (is_numeric($token)) {
$stack->push($token);
Expand Down
21 changes: 21 additions & 0 deletions components/ILIAS/Math/tests/ilMathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class ilMathTest extends TestCase
*/
protected function setUp(): void
{
ilMath::setDefaultAdapter(ilMath::getFirstValidAdapter());
$this->eval_math = new EvalMath();
}

Expand All @@ -51,4 +52,24 @@ public static function gcdData(): array
['41414124', '41414124', '41414124']
];
}

public function testPlusScientificNotationIsPowerOfTen(): void
{
$this->assertEqualsWithDelta(100.0, (float) $this->eval_math->evaluate('1E+2'), 0.000001);
$this->assertEqualsWithDelta(5400000.0, (float) $this->eval_math->evaluate('5.4E+6'), 0.001);
$this->assertEqualsWithDelta(5400000.0, (float) $this->eval_math->evaluate('54E5'), 0.001);
$this->assertEqualsWithDelta(0.0054, (float) $this->eval_math->evaluate('5.4E-3'), 0.0000001);

$lower = (float) $this->eval_math->evaluate('5.4E+24');
$upper = (float) $this->eval_math->evaluate('5.6E+24');
$this->assertGreaterThan(38000.0, $lower);
$this->assertGreaterThan($lower, $upper);

if (extension_loaded('bcmath')) {
$lower_text = (string) $this->eval_math->evaluate('5.4E+24');
$upper_text = (string) $this->eval_math->evaluate('5.6E+24');
$this->assertSame(0, bccomp($lower_text, '5400000000000000000000000', 0), $lower_text);
$this->assertSame(0, bccomp($upper_text, '5600000000000000000000000', 0), $upper_text);
}
}
}
4 changes: 2 additions & 2 deletions components/ILIAS/Refinery/src/KindlyTo/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ public function int(): Transformation
* This supports:
* - strings in natural notation matching \s*(0|(-?[1-9]\d*([.,]\d+)?))\s*,
* trimming is supported
* - strings in floating point representation matching \s*-?\d+[eE]-?\d+\s*,
* trimming is supported
* - strings in floating point representation matching \s*-?\d+(?:[.,]\d+)?[eE][+-]?\d+\s*,
* trimming is supported. A comma in the mantissa is a decimal separator.
* - ints, which will be typecasted to float
* - bools, where true maps to 1.0 and false to 0.0
* This doesn't support:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class FloatTransformation implements Transformation
use DeriveApplyToFromTransform;
use DeriveInvokeFromTransform;
private const REG_STRING = '/^\s*(-?(0|([1-9]\d*)))([.,]\d*)?\s*$/';
private const REG_STRING_FLOATING = '/^\s*-?\d+[eE]-?\d+\s*$/';
private const REG_STRING_FLOATING = '/^\s*-?\d+(?:[.,]\d+)?[eE][+-]?\d+\s*$/';

/**
* @inheritDoc
Expand All @@ -57,7 +57,7 @@ public function transform($from): float

$preg_match_floating_string = preg_match(self::REG_STRING_FLOATING, $from, $RegMatch);
if ($preg_match_floating_string) {
return (float) $from;
return (float) str_replace(',', '.', $from);
}

throw new ConstraintViolationException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,22 @@ public static function FloatTestDataProvider(): array
'floatstr_st_one' => ['0.1', 0.1],
'floatstr_st_one_negative' => ['-0.1', -0.1],
'floatstr_st_one_comma' => ['0,1', 0.1],
'floatstr_st_one_comma_negative' => ['-0,1', -0.1]
'floatstr_st_one_comma_negative' => ['-0,1', -0.1],
'decimal_mantissa_plus_exponent' => ['5.4E+24', 5.4E+24],
'comma_mantissa_plus_exponent' => ['5,4E+24', 5.4E+24],
'integer_mantissa_plus_exponent' => ['54E+23', 5.4E+24],
'decimal_mantissa_minus_exponent' => ['5.4E-3', 0.0054],
'upper_bound_from_report' => ['5.6E+24', 5.6E+24]
];
}

public function testPhpStringFormOfScientificBoundParsesAgain(): void
{
$parsed = $this->transformation->transform('54E23');
$again = $this->transformation->transform((string) $parsed);

$this->assertEquals(5.4E+24, $parsed);
$this->assertEquals($parsed, $again);
$this->assertEquals('5.4E+24', (string) $parsed);
}
}
Loading