diff --git a/components/ILIAS/Math/classes/class.EvalMath.php b/components/ILIAS/Math/classes/class.EvalMath.php index 8b5fc56cfda4..9f4ee6179438 100755 --- a/components/ILIAS/Math/classes/class.EvalMath.php +++ b/components/ILIAS/Math/classes/class.EvalMath.php @@ -1,5 +1,21 @@ $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 @@ -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 @@ -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 = '*'; @@ -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)) { @@ -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)) { @@ -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]; @@ -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) { @@ -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: @@ -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); diff --git a/components/ILIAS/Math/tests/ilMathTest.php b/components/ILIAS/Math/tests/ilMathTest.php index 2c953f43a5ea..4fe564730312 100755 --- a/components/ILIAS/Math/tests/ilMathTest.php +++ b/components/ILIAS/Math/tests/ilMathTest.php @@ -30,6 +30,7 @@ class ilMathTest extends TestCase */ protected function setUp(): void { + ilMath::setDefaultAdapter(ilMath::getFirstValidAdapter()); $this->eval_math = new EvalMath(); } @@ -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); + } + } } diff --git a/components/ILIAS/Refinery/src/KindlyTo/Group.php b/components/ILIAS/Refinery/src/KindlyTo/Group.php index 7d241286fd43..2692780b36f6 100755 --- a/components/ILIAS/Refinery/src/KindlyTo/Group.php +++ b/components/ILIAS/Refinery/src/KindlyTo/Group.php @@ -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: diff --git a/components/ILIAS/Refinery/src/KindlyTo/Transformation/FloatTransformation.php b/components/ILIAS/Refinery/src/KindlyTo/Transformation/FloatTransformation.php index b8cd71d8aa18..fe55ec17c400 100755 --- a/components/ILIAS/Refinery/src/KindlyTo/Transformation/FloatTransformation.php +++ b/components/ILIAS/Refinery/src/KindlyTo/Transformation/FloatTransformation.php @@ -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 @@ -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( diff --git a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php index da2c1377a536..3dd914592524 100755 --- a/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php +++ b/components/ILIAS/Refinery/tests/KindlyTo/Transformation/FloatTransformationTest.php @@ -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); + } }