Permalink
Browse files

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.
  • Loading branch information...
1 parent bac15e5 commit ff43c30f0aeaa66afa9cafe27788de309ff3dedb @timhunt timhunt committed Aug 20, 2012
Showing with 42 additions and 8 deletions.
  1. +5 −3 question/type/numerical/question.php
  2. +37 −5 question/type/numerical/tests/answer_test.php
@@ -320,19 +320,21 @@ public function get_tolerance_interval() {
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':
$range = abs($this->answer) * $tolerance;
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':
@@ -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 @@ public function test_within_tolerance_nominal() {
$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() {

0 comments on commit ff43c30

Please sign in to comment.