From ab40748b5aa24ece6fb1e7eb8bfce24fdbbce286 Mon Sep 17 00:00:00 2001 From: Feanil Patel Date: Thu, 24 Oct 2019 15:28:09 -0400 Subject: [PATCH] Add an implementation of python2 style round. Python 3 has no way to access this implementation of rounding in the standard library. We implement it here so that we can continue to use it for grades calculation to keep regrading consistent. Currently we can't be confident that if we change the rounding behaviour we won't impact students. We can't be sure that students that were previously passing wouldn't suddenly no longer be passing. Given this, it's lower risk to just implement the old rounding strategy here and use it when we are rounding to calculate grades. --- openedx/core/lib/grade_utils.py | 24 ++++++++++++++++++++++ openedx/core/lib/tests/test_grade_utils.py | 14 ++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/openedx/core/lib/grade_utils.py b/openedx/core/lib/grade_utils.py index 601b994d13..2e7073e962 100644 --- a/openedx/core/lib/grade_utils.py +++ b/openedx/core/lib/grade_utils.py @@ -1,6 +1,7 @@ """ Helpers functions for grades and scores. """ +import math def compare_scores(earned1, possible1, earned2, possible2, treat_undefined_as_zero=False): @@ -42,3 +43,26 @@ def is_score_higher_or_equal(earned1, possible1, earned2, possible2, treat_undef """ is_higher_or_equal, _, _ = compare_scores(earned1, possible1, earned2, possible2, treat_undefined_as_zero) return is_higher_or_equal + + +def round_away_from_zero(number, digits=0): + """ + Round numbers using the 'away from zero' strategy as opposed to the + 'Banker's rounding strategy.' The strategy refers to how we round when + a number is half way between two numbers. eg. 0.5, 1.5, etc. In python 2 + positive numbers in this category would be rounded up and negative numbers + would be rounded down. ie. away from zero. In python 3 numbers round + towards even. So 0.5 would round to 0 but 1.5 would round to 2. + + See here for more on floating point rounding strategies: + https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules + + We want to continue to round away from zero so that student grades remain + consistent and don't suddenly change. + """ + p = 10.0 ** digits + + if number >= 0: + return float(math.floor((number * p) + 0.5)) / p + else: + return float(math.ceil((number * p) - 0.5)) / p diff --git a/openedx/core/lib/tests/test_grade_utils.py b/openedx/core/lib/tests/test_grade_utils.py index 9c4f08216d..4d6dd00912 100644 --- a/openedx/core/lib/tests/test_grade_utils.py +++ b/openedx/core/lib/tests/test_grade_utils.py @@ -7,7 +7,7 @@ from unittest import TestCase import ddt -from ..grade_utils import compare_scores +from ..grade_utils import compare_scores, round_away_from_zero @ddt.ddt @@ -45,3 +45,15 @@ class TestGradeUtils(TestCase): assert is_higher is True assert 0 == percentage_1 assert 0 == percentage_2 + + @ddt.data( + (0.5, 1), + (1.45, 1.5, 1), + (-0.5, -1.0), + (-0.1, -0.0), + (0.1, 0.0), + (0.0, 0.0) + ) + @ddt.unpack + def test_round_away_from_zero(self, precise, expected_rounded_number, rounding_precision=0): + assert round_away_from_zero(precise, rounding_precision) == expected_rounded_number