use BigDecimal to compute numeric error margin

test plan:
 - create numeric answer questions with error margins
 - copy the course
 - in the copied quiz, the error margins should match the
   original course (show as 0.0001 and not something like
   0.00009999999999889)

fixes CNVS-19615

Change-Id: Iba9e40f9e37323310e6414794139c68d0f1c061c
Reviewed-on: https://gerrit.instructure.com/51334
Reviewed-by: James Williams  <jamesw@instructure.com>
Tested-by: Jenkins
QA-Review: Clare Strong <clare@instructure.com>
Product-Review: Jeremy Stanley <jeremy@instructure.com>
This commit is contained in:
Jeremy Stanley 2015-03-31 16:16:41 -06:00
parent 84215a4e09
commit 01b8bae52d
3 changed files with 47 additions and 8 deletions

View File

@ -1,3 +1,5 @@
require 'bigdecimal'
module Qti
class NumericInteraction < AssessmentItemConverter
def initialize(opts={})
@ -39,11 +41,13 @@ class NumericInteraction < AssessmentItemConverter
exact_node = or_node.at_css('stringMatch baseValue')
next unless exact_node
answer[:numerical_answer_type] = 'exact_answer'
exact = exact_node.text.to_f rescue 0.0
answer[:exact] = exact
exact = exact_node.text rescue "0.0"
answer[:exact] = exact.to_f
if upper = or_node.at_css('and customOperator[class=varlte] baseValue')
margin = upper.text.to_f - exact rescue 0.0
answer[:margin] = margin
# do margin computation with BigDecimal to avoid rounding errors
# (this is also used when _scoring_ numeric range questions)
margin = BigDecimal.new(upper.text) - BigDecimal.new(exact) rescue "0.0"
answer[:margin] = margin.to_f
end
@question[:answers] << answer
elsif and_node = r_if.at_css('and')

View File

@ -15,6 +15,8 @@
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require 'bigdecimal'
module CC
module QTI
module QTIItems
@ -340,9 +342,10 @@ module CC
or_node.varequal exact, :respident=>"response1"
unless answer['margin'].blank?
or_node.and do |and_node|
margin = answer['margin'].to_f
and_node.vargte(exact - margin, :respident=>"response1")
and_node.varlte(exact + margin, :respident=>"response1")
exact = BigDecimal.new(answer['exact'].to_s)
margin = BigDecimal.new(answer['margin'].to_s)
and_node.vargte((exact - margin).to_f, :respident=>"response1")
and_node.varlte((exact + margin).to_f, :respident=>"response1")
end
end
end

View File

@ -650,6 +650,38 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
expect(decoy_assignment_group.reload.name).not_to eql group.name
end
it "should round numeric answer margins sanely" do
q = @copy_from.quizzes.create!(:title => "blah")
# this one targets rounding errors in gems/plugins/qti_exporter/lib/qti/numeric_interaction.rb (import side)
data1 = {:question_type => "numerical_question",
:question_text => "what is the optimal matter/antimatter intermix ratio",
:answers => [{
:text => "answer_text",
:weight => 100,
:numerical_answer_type => "exact_answer",
:answer_exact => 1,
:answer_error_margin => 0.0001
}]}.with_indifferent_access
# this one targets rounding errors in lib/cc/qti/qti_items.rb (export side)
data2 = {:question_type => "numerical_question",
:question_text => "what is the airspeed velocity of an unladed African swallow",
:answers => [{
:text => "answer_text",
:weight => 100,
:numerical_answer_type => "exact_answer",
:answer_exact => 2.0009,
:answer_error_margin => 0.0001
}]}.with_indifferent_access
q.quiz_questions.create!(:question_data => data1)
q.quiz_questions.create!(:question_data => data2)
run_course_copy
q2 = @copy_to.quizzes.where(migration_id: mig_id(q)).first
expect(q2.quiz_questions[0].question_data["answers"][0]["margin"].to_s).to eq "0.0001"
expect(q2.quiz_questions[1].question_data["answers"][0]["margin"].to_s).to eq "0.0001"
end
it "should not combine when copying question banks with the same title" do
data = {'question_name' => 'test question 1', 'question_type' => 'essay_question', 'question_text' => 'blah'}
@ -685,7 +717,7 @@ equation: <img class="equation_image" title="Log_216" src="/equation_images/Log_
expect(group2_copy.assessment_question_bank_id).to eq bank2_copy.id
end
it "hould copy stuff" do
it "should copy stuff" do
data1 = {:question_type => "file_upload_question",
:points_possible => 10,
:question_text => "why is this question terrible"