Evaluating real value answers in exercise generator applications.
One of the problems, that you encounter, when writing math or scientific applications, is the comparison of real numbers. Due to their internal representation, it is a common situation that two values, that should be the same, return a "not equal" condition when comparing them with the "=" operator. In applications that generate exercises, where the user has to enter real value results, there is a further problem: How many decimal digits the user has to use in calculations and how many decimal digits they must enter to get their answer evaluated as "correct" by the program? And how to implement this "depending on the number of decimal digits" equality?
A general rule is to never use the equality operator to check if two real numbers are equal. Instead, check if the difference of the numbers is less
than a given value ε, where ε defines the precision, i.e. the number of decimal digits for which the two numbers have to be equal. Example: To check if two reals
A and B are equal with a precision of 3 decimal digits, check if the difference between the two numbers is greater than 10-3. In Free Pascal you can implement this
by the statement
if Abs(A - B) > 0.001 then ...
Here an example in my Transistor switches application. Used as an exercise generator, the user is supposed to
enter answer values with a precision of 3 decimal digits. If rR1 is the calculated value of resistance R1 and rUR1 the R1 value entered by the
user, you can use the following code to evaluate if the user answer is correct or false:
// Check resistance R1
if Abs(rR1 - rUR1) > 0.001 then begin
edR1.Text := FloatToStr(rR1);
edR1.Font.Color := clRed;
Wrong := True;
end;
I think that the best way to avoid that correct answers to exercise generator questions are evaluated as false, is to consider that the user uses the full precision number (some 7 digits or as given by the calculator used) in calculations (in particular in sub-results) and that you let them know what's the precision (number of decimal digits) the application expects when checking the answer values. Checking the answer values may be done as in the example above; another possibility is to use the function RFormat, described in my article Formatted output of real numbers. Applying the function to both the calculated value and the user input, you can use the equality operator to compare 2 strings.
Here the user answer evaluation code of my Static equilibrium: Balancing the forces acting upon an object
application:
OK := True;
// If one of the answer values is false, the global user answer is false...
if (edVectorRN.Text = '') or (edAngleRN.Text = '') or (edVectorRB.Text = '') or (edAngleRB.Text = '') then
OK := False
else if (edVectorX.Text = '') or (edAngleX.Text = '') or (edVectorY.Text = '') or (edAngleY.Text = '') then
OK := False
else if (RFormat(StrToFloat(edVectorRN.Text), 3) <> RFormat(rMagnitudeRN, 3)) or (RFormat(StrToFloat(edAngleRN.Text), 3) <> RFormat(rAngleRN, 3)) then
OK := False
else if (RFormat(StrToFloat(edVectorRB.Text), 3) <> RFormat(rMagnitudeRB, 3)) or (RFormat(StrToFloat(edAngleRB.Text), 3) <> RFormat(rAngleRB, 3)) then
OK := False
else if (RFormat(StrToFloat(edVectorX.Text), 3) <> RFormat(rMagnitudeX, 3)) or (RFormat(StrToFloat(edAngleX.Text), 3) <> RFormat(rAngleX, 3)) then
OK := False
else if (RFormat(StrToFloat(edVectorY.Text), 3) <> RFormat(rMagnitudeY, 3)) or (RFormat(StrToFloat(edAngleY.Text), 3) <> RFormat(rAngleY, 3)) then
OK := False;
// Display "correct" resp. "false" picture
if OK then
imEval.Picture.LoadFromFile('correct.png')
else
imEval.Picture.LoadFromFile('false.png');
imEval.Visible := True;
Sometimes, the problem looks somewhat different: You want that your result values actually have a given maximum of decimal digits. As an
example, my Electronics trainer - Ohm's Law application, where I want that my resistances are integer values and
that the current values don't have more than 2 decimal digits. Here the code of the parallel resistances generation routine:
{ Individual resistances for parallel circuit } | |
procedure ParallelResistances(U, R: Real; var R1, R2: Real); | |
var | |
Count: Integer; | |
begin | |
Count := 0; | |
repeat | |
repeat | |
R1 := Int(Random(Round(8.9 * R)) + 1.1 * R); | // R1 between 1.1 times and 10 times the values of R |
until R1 > R; | // R1 must be greater than R! |
R2 := 1 / ((1 / R) - (1 / R1)); | // R2 as given by parallel circuit formula |
if Count > 2500 then begin | |
R1 := 0; R2 := 0; | // set resistances to "error" (calculation will be done again) |
end; | |
until (Count > 2500) or ((R1 = Int(R1)) and (R2 = Int(R2)) | // both resistances should be integer numbers |
and (100 * U / R1 = Int(100 * U / R1)) and (100 * U / R2 = Int(100 * U / R2))); | // both currents should not have more than 2 decimal digits |
end; |
If you find this text helpful, please, support me and this website by signing my guestbook.