This commit is contained in:
Dan Poltawski
2013-06-17 14:53:19 +08:00
9 changed files with 194 additions and 129 deletions
+17 -19
View File
@@ -139,7 +139,7 @@ class question_dataset_dependent_items_form extends question_wizard_form {
$name = get_string('wildcard', 'qtype_calculated', $datasetdef->name);
}
$mform->addElement('text', "number[$j]", $name);
$mform->setType("number[$j]", PARAM_FLOAT);
$mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
$this->qtypeobj->custom_generator_tools_part($mform, $idx, $j);
$idx++;
$mform->addElement('hidden', "definition[$j]");
@@ -161,11 +161,10 @@ class question_dataset_dependent_items_form extends question_wizard_form {
$answers = fullclone($this->question->options->answers);
$key1 =1;
foreach ($answers as $key => $answer) {
if ('' === $answer->answer) {
// Do nothing.
} else if ('*' === $answer->answer) {
$ans = shorten_text($answer->answer, 17, true);
if ($ans === '*') {
$mform->addElement('static',
'answercomment[' . ($this->noofitems+$key1) . ']', $answer->answer);
'answercomment[' . ($this->noofitems+$key1) . ']', $ans);
$mform->addElement('hidden', 'tolerance['.$key.']', '');
$mform->setType('tolerance['.$key.']', PARAM_RAW);
$mform->setAdvanced('tolerance['.$key.']', true);
@@ -178,9 +177,9 @@ class question_dataset_dependent_items_form extends question_wizard_form {
$mform->addElement('hidden', 'correctanswerformat['.$key.']', '');
$mform->setType('correctanswerformat['.$key.']', PARAM_RAW);
$mform->setAdvanced('correctanswerformat['.$key.']', true);
} else {
} else if ( $ans !== '' ) {
$mform->addElement('static', 'answercomment[' . ($this->noofitems+$key1) . ']',
$answer->answer);
$ans);
$mform->addElement('text', 'tolerance['.$key.']',
get_string('tolerance', 'qtype_calculated'));
$mform->setType('tolerance['.$key.']', PARAM_RAW);
@@ -290,7 +289,7 @@ class question_dataset_dependent_items_form extends question_wizard_form {
} else {
$mform->addElement('hidden', "number[$j]" , '');
}
$mform->setType("number[$j]", PARAM_FLOAT);
$mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
$mform->addElement('hidden', "itemid[$j]");
$mform->setType("itemid[$j]", PARAM_INT);
@@ -312,11 +311,11 @@ class question_dataset_dependent_items_form extends question_wizard_form {
'Formulas {=..} in question text');
foreach ($textequations as $key => $equation) {
if ($formulaerrors = qtype_calculated_find_formula_errors($equation)) {
$str=$formulaerrors;
$str = $formulaerrors;
} else {
eval('$str = '.$equation.';');
}
$equation = shorten_text($equation, 17, true);
$mform->addElement('static', "textequation", "{=$equation}", "=".$str);
}
}
@@ -485,19 +484,18 @@ class question_dataset_dependent_items_form extends question_wizard_form {
$numbers = $data['number'];
foreach ($numbers as $key => $number) {
if (! is_numeric($number)) {
if (stristr($number, ', ')) {
$errors['number['.$key.']'] = get_string(
'The , cannot be used, use . as in 0.013 or 1.3e-2', 'qtype_calculated');
if (stristr($number, ',')) {
$errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
} else {
$errors['number['.$key.']'] = get_string(
'This is not a valid number', 'qtype_calculated');
$errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
}
} else if (stristr($number, 'x')) {
$errors['number['.$key.']'] = get_string(
'Hexadecimal format (i.e. 0X12d) is not allowed', 'qtype_calculated');
$a = new stdClass();
$a->name = '';
$a->value = $number;
$errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
} else if (is_nan($number)) {
$errors['number['.$key.']'] = get_string(
'is a NAN number', 'qtype_calculated');
$errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
}
}
return $errors;
+80 -3
View File
@@ -86,6 +86,25 @@ class qtype_calculated_question extends qtype_numerical_question
return parent::get_variants_selection_seed();
}
}
public function get_correct_response() {
$answer = $this->get_correct_answer();
if (!$answer) {
return array();
}
$response = array('answer' => $this->vs->format_float($answer->answer,
$answer->correctanswerlength, $answer->correctanswerformat));
if ($this->has_separate_unit_field()) {
$response['unit'] = $this->ap->get_default_unit();
} else if ($this->unitdisplay == qtype_numerical::UNITINPUT) {
$response['answer'] = $this->ap->add_unit($response['answer']);
}
return $response;
}
}
@@ -318,9 +337,67 @@ class qtype_calculated_variable_substituter {
if ($format == '1' ) { // Answer is to have $length decimals.
// Decimal places.
$x = sprintf('%.' . $length . 'F', $x);
} else if ($format == 2) {
// Significant figures.
$x = sprintf('%.' . $length . 'g', $x);
} else if ($x) { // Significant figures does only apply if the result is non-zero.
$answer = $x;
// Convert to positive answer.
if ($answer < 0) {
$answer = -$answer;
$sign = '-';
} else {
$sign = '';
}
// Determine the format 0.[1-9][0-9]* for the answer...
$p10 = 0;
while ($answer < 1) {
--$p10;
$answer *= 10;
}
while ($answer >= 1) {
++$p10;
$answer /= 10;
}
// ... and have the answer rounded of to the correct length.
$answer = round($answer, $length);
// If we rounded up to 1.0, place the answer back into 0.[1-9][0-9]* format.
if ($answer >= 1) {
++$p10;
$answer /= 10;
}
// Have the answer written on a suitable format.
// Either scientific or plain numeric.
if (-2 > $p10 || 4 < $p10) {
// Use scientific format.
$exponent = 'e'.--$p10;
$answer *= 10;
if (1 == $length) {
$x = $sign.$answer.$exponent;
} else {
// Attach additional zeros at the end of $answer.
$answer .= (1 == strlen($answer) ? '.' : '')
. '00000000000000000000000000000000000000000x';
$x = $sign
.substr($answer, 0, $length +1).$exponent;
}
} else {
// Stick to plain numeric format.
$answer *= "1e$p10";
if (0.1 <= $answer / "1e$length") {
$x = $sign.$answer;
} else {
// Could be an idea to add some zeros here.
$answer .= (preg_match('~^[0-9]*$~', $answer) ? '.' : '')
. '00000000000000000000000000000000000000000x';
$oklen = $length + ($p10 < 1 ? 2-$p10 : 1);
$x = $sign.substr($answer, 0, $oklen);
}
}
} else {
$x = 0.0;
}
}
return str_replace('.', $this->decimalpoint, $x);
+14 -87
View File
@@ -1032,11 +1032,8 @@ class qtype_calculated extends question_type {
$answers = $question->options->answers;
foreach ($answers as $key => $answer) {
if (is_string($answer)) {
$strheader .= $delimiter.$answer;
} else {
$strheader .= $delimiter.$answer->answer;
}
$ans = shorten_text($answer->answer, 17, true);
$strheader .= $delimiter.$ans;
$delimiter = '<br/><br/><br/>';
}
return $strheader;
@@ -1083,10 +1080,11 @@ class qtype_calculated extends question_type {
$comment->stranswers[$key] = $formula . ' = ' .
get_string('anyvalue', 'qtype_calculated') . '<br/><br/><br/>';
} else {
$formula = shorten_text($formula, 57, true);
$comment->stranswers[$key] = $formula . ' = ' . $formattedanswer->answer . '<br/>';
$correcttrue = new stdClass();
$correcttrue->correct = $formattedanswer->answer;
$correcttrue->true = $answer->answer;
$correcttrue->true = '';
if ($formattedanswer->answer < $answer->min ||
$formattedanswer->answer > $answer->max) {
$comment->outsidelimit = true;
@@ -1106,57 +1104,6 @@ class qtype_calculated extends question_type {
}
return fullclone($comment);
}
public function multichoice_comment_on_datasetitems($questionid, $questiontext,
$answers, $data, $number) {
global $DB;
$comment = new stdClass();
$comment->stranswers = array();
$comment->outsidelimit = false;
$comment->answers = array();
// Find a default unit.
if (!empty($questionid) && $unit = $DB->get_record('question_numerical_units',
array('question' => $questionid, 'multiplier' => 1.0))) {
$unit = $unit->unit;
} else {
$unit = '';
}
$answers = fullclone($answers);
$errors = '';
$delimiter = ': ';
foreach ($answers as $key => $answer) {
$answer->answer = $this->substitute_variables($answer->answer, $data);
// Evaluate the equations i.e {=5+4).
$qtext = '';
$qtextremaining = $answer->answer;
while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
$qtextsplits = explode($regs1[0], $qtextremaining, 2);
$qtext = $qtext.$qtextsplits[0];
$qtextremaining = $qtextsplits[1];
if (empty($regs1[1])) {
$str = '';
} else {
if ($formulaerrors = qtype_calculated_find_formula_errors($regs1[1])) {
$str = $formulaerrors;
} else {
eval('$str = '.$regs1[1].';');
$texteval= qtype_calculated_calculate_answer(
$str, $data, $answer->tolerance,
$answer->tolerancetype, $answer->correctanswerlength,
$answer->correctanswerformat, '');
$str = $texteval->answer;
}
}
$qtext = $qtext.$str;
}
$answer->answer = $qtext.$qtextremaining;
$comment->stranswers[$key]= $answer->answer;
}
return fullclone($comment);
}
public function tolerance_types() {
return array(
@@ -1878,32 +1825,9 @@ function qtype_calculated_calculate_answer($formula, $individualdata,
$calculated->answer = NAN;
return $calculated;
}
if ('1' == $answerformat) { /* Answer is to have $answerlength decimals */
/*** Adjust to the correct number of decimals ***/
if (stripos($answer, 'e')>0) {
$answerlengthadd = strlen($answer)-stripos($answer, 'e');
} else {
$answerlengthadd = 0;
}
$calculated->answer = round(floatval($answer), $answerlength+$answerlengthadd);
if ($answerlength) {
/* Try to include missing zeros at the end */
if (preg_match('~^(.*\\.)(.*)$~', $calculated->answer, $regs)) {
$calculated->answer = $regs[1] . substr(
$regs[2] . '00000000000000000000000000000000000000000x',
0, $answerlength)
. $unit;
} else {
$calculated->answer .=
substr('.00000000000000000000000000000000000000000x',
0, $answerlength + 1) . $unit;
}
} else {
/* Attach unit */
$calculated->answer .= $unit;
}
if ('1' == $answerformat) { // Answer is to have $answerlength decimals.
// Decimal places.
$calculated->answer = sprintf('%.' . $answerlength . 'F', $answer);
} else if ($answer) { // Significant figures does only apply if the result is non-zero.
@@ -1941,31 +1865,34 @@ function qtype_calculated_calculate_answer($formula, $individualdata,
$exponent = 'e'.--$p10;
$answer *= 10;
if (1 == $answerlength) {
$calculated->answer = $sign.$answer.$exponent.$unit;
$calculated->answer = $sign.$answer.$exponent;
} else {
// Attach additional zeros at the end of $answer.
$answer .= (1 == strlen($answer) ? '.' : '')
. '00000000000000000000000000000000000000000x';
$calculated->answer = $sign
.substr($answer, 0, $answerlength +1).$exponent.$unit;
.substr($answer, 0, $answerlength +1).$exponent;
}
} else {
// Stick to plain numeric format.
$answer *= "1e$p10";
if (0.1 <= $answer / "1e$answerlength") {
$calculated->answer = $sign.$answer.$unit;
$calculated->answer = $sign.$answer;
} else {
// Could be an idea to add some zeros here.
$answer .= (preg_match('~^[0-9]*$~', $answer) ? '.' : '')
. '00000000000000000000000000000000000000000x';
$oklen = $answerlength + ($p10 < 1 ? 2-$p10 : 1);
$calculated->answer = $sign.substr($answer, 0, $oklen).$unit;
$calculated->answer = $sign.substr($answer, 0, $oklen);
}
}
} else {
$calculated->answer = 0.0;
}
if ($unit != '') {
$calculated->answer = $calculated->answer . ' ' . $unit;
}
// Return the result.
return $calculated;
+15
View File
@@ -36,4 +36,19 @@ require_once($CFG->dirroot . '/question/type/numerical/renderer.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_calculated_renderer extends qtype_numerical_renderer {
public function correct_response(question_attempt $qa) {
$question = $qa->get_question();
$answer = $question->get_correct_response();
if (!$answer) {
return '';
}
$response = $answer['answer'];
if ($question->unitdisplay != qtype_numerical::UNITNONE && $question->unitdisplay != qtype_numerical::UNITINPUT) {
$response = $question->ap->add_unit($response);
}
return get_string('correctansweris', 'qtype_shortanswer', $response);
}
}
@@ -28,6 +28,8 @@ defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/question/type/calculated/question.php');
require_once($CFG->dirroot . '/question/type/numerical/question.php');
require_once($CFG->dirroot . '/question/type/numerical/questiontype.php');
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
@@ -75,6 +77,8 @@ class qtype_calculated_test_helper extends question_test_helper {
$q->datasetloader = new qtype_calculated_test_dataset_loader(0, array(
array('a' => 1, 'b' => 5),
array('a' => 3, 'b' => 4),
array('a' => 3, 'b' => 0.01416),
array('a' => 31, 'b' => 0.01416),
));
return $q;
@@ -67,12 +67,41 @@ class qtype_calculated_question_test extends advanced_testcase {
}
public function test_get_correct_response() {
// Testing with 3.0 + 0.1416.
$question = test_question_maker::make_question('calculated');
$question->start_attempt(new question_attempt_step(), 3);
$values = $question->vs->get_values();
$this->assertSame(array('answer' => '3.01' ), $question->get_correct_response());
foreach ($question->answers as $answer) {
$answer->correctanswerlength = 2;
$answer->correctanswerformat = 2;
}
$this->assertSame(array('answer' => '3.0' ), $question->get_correct_response());
// Testing with 1.0 + 5.0.
$question = test_question_maker::make_question('calculated');
$question->start_attempt(new question_attempt_step(), 1);
$values = $question->vs->get_values();
$this->assertSame(array('answer' => '6.00' ), $question->get_correct_response());
$this->assertEquals(array('answer' => $values['a'] + $values['b']),
foreach ($question->answers as $answer) {
$answer->correctanswerlength = 2;
$answer->correctanswerformat = 2;
}
$this->assertSame(array('answer' => '6.0' ),
$question->get_correct_response());
// Testing with 31.0 + 0.01416 .
$question = test_question_maker::make_question('calculated');
$question->start_attempt(new question_attempt_step(), 4);
$values = $question->vs->get_values();
$this->assertSame(array('answer' => '31.01' ), $question->get_correct_response());
foreach ($question->answers as $answer) {
$answer->correctanswerlength = 3;
$answer->correctanswerformat = 2;
}
$this->assertSame(array('answer' => '31.0' ), $question->get_correct_response());
}
public function test_get_question_summary() {
+13 -16
View File
@@ -197,12 +197,9 @@ class qtype_calculatedmulti extends qtype_calculated {
$answers = $question->options->answers;
foreach ($answers as $key => $answer) {
if (is_string($answer)) {
$strheader .= $delimiter.$answer;
} else {
$strheader .= $delimiter.$answer->answer;
}
$delimiter = '<br/>';
$ans = shorten_text($answer->answer, 17, true);
$strheader .= $delimiter.$ans;
$delimiter = '<br/><br/>';
}
return $strheader;
}
@@ -219,14 +216,14 @@ class qtype_calculatedmulti extends qtype_calculated {
$errors = '';
$delimiter = ': ';
foreach ($answers as $key => $answer) {
$answer->answer = $this->substitute_variables($answer->answer, $data);
$anssubstituted = $this->substitute_variables($answer->answer, $data);
// Evaluate the equations i.e {=5+4).
$qtext = '';
$qtextremaining = $answer->answer;
while (preg_match('~\{=([^[:space:]}]*)}~', $qtextremaining, $regs1)) {
$qtextsplits = explode($regs1[0], $qtextremaining, 2);
$qtext =$qtext.$qtextsplits[0];
$qtextremaining = $qtextsplits[1];
$anstext = '';
$anstextremaining = $anssubstituted;
while (preg_match('~\{=([^[:space:]}]*)}~', $anstextremaining, $regs1)) {
$anstextsplits = explode($regs1[0], $anstextremaining, 2);
$anstext =$anstext.$anstextsplits[0];
$anstextremaining = $anstextsplits[1];
if (empty($regs1[1])) {
$str = '';
} else {
@@ -236,10 +233,10 @@ class qtype_calculatedmulti extends qtype_calculated {
eval('$str = '.$regs1[1].';');
}
}
$qtext = $qtext.$str;
$anstext = $anstext.$str;
}
$answer->answer = $qtext.$qtextremaining;
$comment->stranswers[$key] = $answer->answer;
$anstext .= $anstextremaining;
$comment->stranswers[$key] = $anssubstituted.'<br/>'.$anstext;
}
return fullclone($comment);
}
@@ -255,7 +255,7 @@ class qtype_calculatedsimple_edit_form extends qtype_calculated_edit_form {
!($datasettoremove ||$newdataset ||$newdatasetvalues)) {
$i = 1;
$fromformdefinition = optional_param_array('definition', '', PARAM_NOTAGS);
$fromformnumber = optional_param_array('number', '', PARAM_INT);
$fromformnumber = optional_param_array('number', '', PARAM_RAW);// This parameter will be validated in the form.
$fromformitemid = optional_param_array('itemid', '', PARAM_INT);
ksort($fromformdefinition);
@@ -508,7 +508,7 @@ class qtype_calculatedsimple_edit_form extends qtype_calculated_edit_form {
$mform->addElement('hidden', "number[$j]", get_string(
'wildcard', 'qtype_calculatedsimple', $datasetdef->name));
}
$mform->setType("number[$j]", PARAM_FLOAT);
$mform->setType("number[$j]", PARAM_RAW); // This parameter will be validated in validation().
$mform->addElement('hidden', "itemid[$j]");
$mform->setType("itemid[$j]", PARAM_INT);
@@ -585,6 +585,23 @@ class qtype_calculatedsimple_edit_form extends qtype_calculated_edit_form {
public function validation($data, $files) {
$errors = parent::validation($data, $files);
$numbers = $data['number'];
foreach ($numbers as $key => $number) {
if (! is_numeric($number)) {
if (stristr($number, ',')) {
$errors['number['.$key.']'] = get_string('nocommaallowed', 'qtype_calculated');
} else {
$errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
}
} else if (stristr($number, 'x')) {
$a = new stdClass();
$a->name = '';
$a->value = $number;
$errors['number['.$key.']'] = get_string('hexanotallowed', 'qtype_calculated', $a);
} else if (is_nan($number)) {
$errors['number['.$key.']'] = get_string('notvalidnumber', 'qtype_calculated');
}
}
if (empty($data['definition'])) {
$errors['selectadd'] = get_string('youmustaddatleastonevalue', 'qtype_calculatedsimple');
@@ -267,7 +267,8 @@ class qtype_calculatedsimple extends qtype_calculated {
$delimiter = '';
foreach ($answers as $key => $answer) {
$strheader .= $delimiter.$answer->answer;
$ans = shorten_text($answer->answer, 17, true);
$strheader .= $delimiter.$ans;
$delimiter = '<br/><br/><br/>';
}
return $strheader;