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);'],