From 07c963dfaafed1519090a053e74222d159faa1a5 Mon Sep 17 00:00:00 2001 From: Philipp Imhof <52650214+PhilippImhof@users.noreply.github.com> Date: Sun, 20 Apr 2025 13:13:00 +0200 Subject: [PATCH 1/3] limit functions allowed in student answers --- classes/local/answer_parser.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/classes/local/answer_parser.php b/classes/local/answer_parser.php index 234e5940..790ed96f 100644 --- a/classes/local/answer_parser.php +++ b/classes/local/answer_parser.php @@ -222,9 +222,14 @@ private function is_acceptable_algebraic_formula(bool $fornumericalformula = fal $answertokens = $this->statements[0]->body; // Iterate over all tokens. If we find a FUNCTION token, we check whether it is in the white list. + // Note: We currently restrict the list of allowed functions to functions with only one argument. + // That assures full backwards compatibility, without limiting our future possibilities w.r.t. the + // usage of the comma as a decimal separator. We do not currently allow the 'log' function (which + // would mean the natural logarithm), because it was not allowed in earlier versions, creates ambiguity + // and would accept two arguments. $functionwhitelist = [ - 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'atan2', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', - 'sqrt', 'exp', 'log', 'log10', 'ln', 'abs', 'ceil', 'floor', 'fact', 'ncr', 'npr', + 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', + 'sqrt', 'exp', 'log10', 'ln', 'abs', 'ceil', 'floor', 'fact', ]; $operatorwhitelist = ['+', '_', '-', '/', '*', '**', '^', '%']; foreach ($answertokens as $token) { From 66794756093ba283449a1dac024ee776129b0cd9 Mon Sep 17 00:00:00 2001 From: Philipp Imhof <52650214+PhilippImhof@users.noreply.github.com> Date: Sun, 20 Apr 2025 13:21:49 +0200 Subject: [PATCH 2/3] update comment --- classes/local/answer_parser.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/local/answer_parser.php b/classes/local/answer_parser.php index 790ed96f..b20c8e03 100644 --- a/classes/local/answer_parser.php +++ b/classes/local/answer_parser.php @@ -177,10 +177,10 @@ private function is_acceptable_numeric(): bool { /** * Check whether the given answer contains only valid tokens for the answer type NUMERICAL_FORMULA, i. e. * - numerical expression - * - plus functions: sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh - * - plus functions: sqrt, exp, log, log10, ln + * - plus functions: sin, cos, tan, asin, acos, atan (but not atan2), sinh, cosh, tanh, asinh, acosh, atanh + * - plus functions: sqrt, exp, log10, ln (but not log) * - plus functions: abs, ceil, floor - * - plus functions: fact, ncr, npr + * - plus functions: fact * - no variables * * @return bool From 5bcc1d54c99332a3f32a1525964070dcb3c1014c Mon Sep 17 00:00:00 2001 From: Philipp Imhof <52650214+PhilippImhof@users.noreply.github.com> Date: Sun, 20 Apr 2025 13:36:31 +0200 Subject: [PATCH 3/3] add lg as synonym for log10 --- classes/local/answer_parser.php | 4 ++-- classes/local/functions.php | 14 ++++++++++++++ script/formatcheck.js | 7 ++++--- tests/answer_parser_test.php | 2 +- tests/evaluator_test.php | 11 ++++++++++- tests/functions_test.php | 3 +++ 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/classes/local/answer_parser.php b/classes/local/answer_parser.php index b20c8e03..ce2107b7 100644 --- a/classes/local/answer_parser.php +++ b/classes/local/answer_parser.php @@ -178,7 +178,7 @@ private function is_acceptable_numeric(): bool { * Check whether the given answer contains only valid tokens for the answer type NUMERICAL_FORMULA, i. e. * - numerical expression * - plus functions: sin, cos, tan, asin, acos, atan (but not atan2), sinh, cosh, tanh, asinh, acosh, atanh - * - plus functions: sqrt, exp, log10, ln (but not log) + * - plus functions: sqrt, exp, log10, ln, lg (but not log) * - plus functions: abs, ceil, floor * - plus functions: fact * - no variables @@ -229,7 +229,7 @@ private function is_acceptable_algebraic_formula(bool $fornumericalformula = fal // and would accept two arguments. $functionwhitelist = [ 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh', 'asinh', 'acosh', 'atanh', - 'sqrt', 'exp', 'log10', 'ln', 'abs', 'ceil', 'floor', 'fact', + 'sqrt', 'exp', 'log10', 'ln', 'lg', 'abs', 'ceil', 'floor', 'fact', ]; $operatorwhitelist = ['+', '_', '-', '/', '*', '**', '^', '%']; foreach ($answertokens as $token) { diff --git a/classes/local/functions.php b/classes/local/functions.php index 2ec2797a..ec9fe22b 100644 --- a/classes/local/functions.php +++ b/classes/local/functions.php @@ -76,6 +76,7 @@ class functions { 'join' => [2, INF], 'lcm' => [2, 2], 'len' => [1, 1], + 'lg' => [1, 1], 'ln' => [1, 1], 'map' => [2, 3], 'modinv' => [2, 2], @@ -1089,6 +1090,19 @@ public static function binomialcdf(float $n, float $p, float $x): float { return $res; } + /** + * Calculate the common logarithm of a number. + * + * @param float $x number + * @return float + */ + public static function lg(float $x): float { + if ($x <= 0) { + self::die('error_func_positive', 'lg()'); + } + return log10($x); + } + /** * Calculate the natural logarithm of a number. * diff --git a/script/formatcheck.js b/script/formatcheck.js index fbb348af..8a2f31d6 100644 --- a/script/formatcheck.js +++ b/script/formatcheck.js @@ -51,13 +51,13 @@ function formulas_format_check() { // The list of allowable (single parameter) function and their evaluation replacement. var funclist = {'sin': 1, 'cos': 1, 'tan': 1, 'asin': 1, 'acos': 1, 'atan': 1, 'exp': 1, - 'log10': 1, 'ln': 1, 'sqrt': 1, 'abs': 1, 'ceil': 1, 'floor': 1, 'fact': 1}; + 'lg': 1, 'log10': 1, 'ln': 1, 'sqrt': 1, 'abs': 1, 'ceil': 1, 'floor': 1, 'fact': 1}; // The replacement list used when preform trial evaluation, note it is not the same as dispreplacelist // Replace 'fact' (factorial) by 'abs', because JS does not have a factorial function and // therefore, an expression containing fact() will always be considered as invalid. We use abs(), // because we just need some replacement function and are not interested in the actual result. - var evalreplacelist = {'ln': 'log', 'log10': '(1./log(10.))*log', 'fact': 'abs'}; // Natural log and log with base 10, no log allowed to avoid ambiguity. + var evalreplacelist = {'lg': 'log10', 'ln': 'log', 'log10': '(1./log(10.))*log', 'fact': 'abs'}; // Natural log and log with base 10, no log allowed to avoid ambiguity. // The replacement list used when the formula is displayed on the screen. var dispreplacelist = {'log10': 'log10', '3.14159265358979323846': 'π'}; @@ -741,6 +741,7 @@ function formulas_format_check() { ['numerical_formula', true, '3+4^-9'], ['numerical_formula', true, '3+exp(4+5)^-sin(6+7)'], ['numerical_formula', true, '1+ln(3)'], + ['numerical_formula', true, '1+lg(3)'], ['numerical_formula', true, '1+log10(3)'], ['numerical_formula', true, 'pi'], ['numerical_formula', false, 'pi()'], @@ -780,7 +781,7 @@ function formulas_format_check() { ['algebraic_formula', true, 'a+b^(c^d)+f'], ['algebraic_formula', true, 'a+(b^c)^d+f'], ['algebraic_formula', true, 'a+b^c^-d'], - ['algebraic_formula', true, '1+ln(a)+log10(b)'], + ['algebraic_formula', true, '1+ln(a)+log10(b)+lg(c)'], ['algebraic_formula', true, 'asin(w t)'], // arcsin(w*t) ['algebraic_formula', true, 'a sin(w t)+ b cos(w t)'], // a*sin(w*t) + b*cos(w*t) ['algebraic_formula', true, '2 (3) a sin(b)^c - (sin(x+y)+x^y)^-sin(z)c tan(z)(x^2)'], diff --git a/tests/answer_parser_test.php b/tests/answer_parser_test.php index 18b28381..b338335f 100644 --- a/tests/answer_parser_test.php +++ b/tests/answer_parser_test.php @@ -128,7 +128,7 @@ public static function provide_algebraic_formulas(): array { [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'a+b^(c^d)+f'], [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'a+(b^c)^d+f'], [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'a+b^c^-d'], - [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, '1+ln(a)+log10(b)'], + [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, '1+ln(a)+log10(b)+lg(c)'], [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'asin(w t)'], [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, 'a sin(w t)+ b cos(w t)'], [qtype_formulas::ANSWER_TYPE_ALGEBRAIC, '2 (3) a sin(b)^c - (sin(x+y)+x^y)^-sin(z)c tan(z)(x^2)'], diff --git a/tests/evaluator_test.php b/tests/evaluator_test.php index 03ac632e..7d851439 100644 --- a/tests/evaluator_test.php +++ b/tests/evaluator_test.php @@ -359,6 +359,7 @@ public static function provide_expressions_with_functions(): array { 'several arguments' => ['foo-bar-test', 'a="foo"; b="bar"; c="test"; join("-", a, b, c)'], 'function with array' => [6, 'sum([1,2,3])'], 'natural logarithm' => [2.708050201102210065996, 'ln(15)'], + 'common logarithm' => [3, 'lg(1000)'], 'nested function' => [-0.35473297204849, 'sin(4) + exp(cos(4+5))'], ]; } @@ -1499,6 +1500,14 @@ public static function provide_invalid_assignments(): array { 'ln() expects its argument to be a positive number.', 'x=ln(0)', ], + 'undefined common logarithm' => [ + 'lg() expects its argument to be a positive number.', + 'x=lg(-5)', + ], + 'undefined common logarithm, 2' => [ + 'lg() expects its argument to be a positive number.', + 'x=lg(0)', + ], 'closing parenthesis when not opened' => [ "1:7:Unbalanced parenthesis, stray ')' found.", 's=fill);', @@ -1990,7 +1999,7 @@ public static function provide_algebraic_formulas(): array { [true, 'a+b^(c^d)+f'], [true, 'a+(b^c)^d+f'], [true, 'a+b^c^-d'], - [true, '1+ln(a)+log10(b)'], + [true, '1+ln(a)+log10(b)+lg(c)'], [true, 'asin(w t / 100)'], [true, 'a sin(w t)+ b cos(w t)'], [true, '2 (3) a sin(b)^c - (sin(x+y)+x^y)^-sin(z)c tan(z)(x^2)'], diff --git a/tests/functions_test.php b/tests/functions_test.php index 5cd93a22..64e34e3e 100644 --- a/tests/functions_test.php +++ b/tests/functions_test.php @@ -914,6 +914,9 @@ public static function provide_algebraic_numerical_function_invocations(): array [true, 'a=log10(0.5);'], [false, 'a=log10();'], [false, 'a=log10(1, 2);'], + [true, 'a=lg(0.5);'], + [false, 'a=lg();'], + [false, 'a=lg(1, 2);'], [true, 'a=log1p(0.5);'], [false, 'a=log1p();'], [false, 'a=log1p(1, 2);'],