MDL-31837 numerical tolerance: better handling of very small tolerances.

The changes between Moodle 1.9 and 2.1 made the marking of very small
answers like 10^-20 almost impossible. This change fixes it.

This fix is almost entirely due the the careful research of Pierre
Pichet, who carefully testing various proposals, and worked out that
this one seemed best.
This commit is contained in:
Tim Hunt
2012-08-23 18:59:15 +01:00
parent bac15e5782
commit ff43c30f0a
2 changed files with 42 additions and 8 deletions
+5 -3
View File
@@ -320,10 +320,13 @@ class qtype_numerical_answer extends question_answer {
throw new coding_exception('Cannot work out tolerance interval for answer *.');
}
// Smallest number that, when added to 1, is different from 1.
$epsilon = pow(10, -1 * ini_get('precision'));
// We need to add a tiny fraction depending on the set precision to make
// the comparison work correctly, otherwise seemingly equal values can
// yield false. See MDL-3225.
$tolerance = (float) $this->tolerance + pow(10, -1 * ini_get('precision'));
$tolerance = abs($this->tolerance) + $epsilon;
switch ($this->tolerancetype) {
case 1: case 'relative':
@@ -331,8 +334,7 @@ class qtype_numerical_answer extends question_answer {
return array($this->answer - $range, $this->answer + $range);
case 2: case 'nominal':
$tolerance = $this->tolerance + pow(10, -1 * ini_get('precision')) *
max(1, abs($this->answer));
$tolerance = $this->tolerance + $epsilon * max(abs($this->tolerance), abs($this->answer), $epsilon);
return array($this->answer - $tolerance, $this->answer + $tolerance);
case 3: case 'geometric':
+37 -5
View File
@@ -25,8 +25,10 @@
*/
global $CFG;
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
require_once($CFG->dirroot . '/question/type/numerical/question.php');
class qtype_numerical_answer_test extends advanced_testcase {
public function test_within_tolerance_nominal() {
$answer = new qtype_numerical_answer(13, 7.0, 1.0, '', FORMAT_MOODLE, 1.0);
@@ -38,16 +40,46 @@ class qtype_numerical_answer_test extends advanced_testcase {
$this->assertFalse($answer->within_tolerance(8.01));
}
public function test_within_tolerance_nominal_zero() {
// Either an answer or tolerance of 0 requires special care. We still
// don't want to end up comparing two floats for absolute equality.
// Zero tol, non-zero answer.
$answer = new qtype_numerical_answer(13, 1e-20, 1.0, '', FORMAT_MOODLE, 0.0);
$this->assertFalse($answer->within_tolerance(0.9999999e-20));
$this->assertTrue($answer->within_tolerance(1e-20));
$this->assertFalse($answer->within_tolerance(1.0000001e-20));
// Non-zero tol, zero answer.
$answer = new qtype_numerical_answer(13, 0.0, 1.0, '', FORMAT_MOODLE, 1e-24);
$this->assertFalse($answer->within_tolerance(-2e-24));
$this->assertTrue($answer->within_tolerance(-1e-24));
$this->assertTrue($answer->within_tolerance(0));
$this->assertTrue($answer->within_tolerance(1e-24));
$this->assertFalse($answer->within_tolerance(2e-24));
// Zero tol, zero answer.
$answer = new qtype_numerical_answer(13, 0.0, 1.0, '', FORMAT_MOODLE, 1e-24);
$this->assertFalse($answer->within_tolerance(-1e-20));
$this->assertTrue($answer->within_tolerance(-1e-35));
$this->assertTrue($answer->within_tolerance(0));
$this->assertTrue($answer->within_tolerance(1e-35));
$this->assertFalse($answer->within_tolerance(1e-20));
// Non-zero tol, non-zero answer.
$answer = new qtype_numerical_answer(13, 1e-20, 1.0, '', FORMAT_MOODLE, 1e-24);
$this->assertFalse($answer->within_tolerance(1.0002e-20));
$this->assertTrue($answer->within_tolerance(1.0001e-20));
$this->assertTrue($answer->within_tolerance(1e-20));
$this->assertTrue($answer->within_tolerance(1.0001e-20));
$this->assertFalse($answer->within_tolerance(1.0002e-20));
}
public function test_within_tolerance_blank() {
$answer = new qtype_numerical_answer(13, 1234, 1.0, '', FORMAT_MOODLE, '');
$this->assertTrue($answer->within_tolerance(1234));
$this->assertFalse($answer->within_tolerance(1234.000001));
$this->assertFalse($answer->within_tolerance(0));
$answer = new qtype_numerical_answer(13, 0, 1.0, '', FORMAT_MOODLE, '');
$this->assertTrue($answer->within_tolerance(0));
$this->assertFalse($answer->within_tolerance(pow(10, -1 * ini_get('precision') + 1)));
$this->assertTrue($answer->within_tolerance(pow(10, -1 * ini_get('precision'))));
}
public function test_within_tolerance_relative() {