-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tolerance expressed in percentage now computes correctly. [BLD-522] #3489
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
"""Tests capa util""" | ||
|
||
import unittest | ||
import textwrap | ||
from . import test_capa_system | ||
from capa.util import compare_with_tolerance | ||
|
||
|
||
class UtilTest(unittest.TestCase): | ||
"""Tests for util""" | ||
def setUp(self): | ||
super(UtilTest, self).setUp() | ||
self.system = test_capa_system() | ||
|
||
def test_compare_with_tolerance(self): | ||
# Test default tolerance '0.001%' (it is relative) | ||
result = compare_with_tolerance(100.0, 100.0) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(100.001, 100.0) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(101.0, 100.0) | ||
self.assertFalse(result) | ||
# Test absolute percentage tolerance | ||
result = compare_with_tolerance(109.9, 100.0, '10%', False) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(110.1, 100.0, '10%', False) | ||
self.assertFalse(result) | ||
# Test relative percentage tolerance | ||
result = compare_with_tolerance(111.0, 100.0, '10%', True) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(112.0, 100.0, '10%', True) | ||
self.assertFalse(result) | ||
# Test absolute tolerance (string) | ||
result = compare_with_tolerance(109.9, 100.0, '10.0', False) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(110.1, 100.0, '10.0', False) | ||
self.assertFalse(result) | ||
# Test relative tolerance (string) | ||
result = compare_with_tolerance(111.0, 100.0, '0.1', True) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(112.0, 100.0, '0.1', True) | ||
self.assertFalse(result) | ||
# Test absolute tolerance (float) | ||
result = compare_with_tolerance(109.9, 100.0, 10.0, False) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(110.1, 100.0, 10.0, False) | ||
self.assertFalse(result) | ||
# Test relative tolerance (float) | ||
result = compare_with_tolerance(111.0, 100.0, 0.1, True) | ||
self.assertTrue(result) | ||
result = compare_with_tolerance(112.0, 100.0, 0.1, True) | ||
self.assertFalse(result) | ||
##### Infinite values ##### | ||
infinity = float('Inf') | ||
# Test relative tolerance (float) | ||
result = compare_with_tolerance(infinity, 100.0, 1.0, True) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(100.0, infinity, 1.0, True) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(infinity, infinity, 1.0, True) | ||
self.assertTrue(result) | ||
# Test absolute tolerance (float) | ||
result = compare_with_tolerance(infinity, 100.0, 1.0, False) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(100.0, infinity, 1.0, False) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(infinity, infinity, 1.0, False) | ||
self.assertTrue(result) | ||
# Test relative tolerance (string) | ||
result = compare_with_tolerance(infinity, 100.0, '1.0', True) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(100.0, infinity, '1.0', True) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(infinity, infinity, '1.0', True) | ||
self.assertTrue(result) | ||
# Test absolute tolerance (string) | ||
result = compare_with_tolerance(infinity, 100.0, '1.0', False) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(100.0, infinity, '1.0', False) | ||
self.assertFalse(result) | ||
result = compare_with_tolerance(infinity, infinity, '1.0', False) | ||
self.assertTrue(result) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,16 +7,29 @@ | |
default_tolerance = '0.001%' | ||
|
||
|
||
def compare_with_tolerance(complex1, complex2, tolerance=default_tolerance, relative_tolerance=False): | ||
def compare_with_tolerance(student_complex, instructor_complex, tolerance=default_tolerance, relative_tolerance=False): | ||
""" | ||
Compare complex1 to complex2 with maximum tolerance tol. | ||
Compare student_complex to instructor_complex with maximum tolerance tolerance. | ||
|
||
If tolerance is type string, then it is counted as relative if it ends in %; otherwise, it is absolute. | ||
- student_complex : student result (float complex number) | ||
- instructor_complex : instructor result (float complex number) | ||
- tolerance : float, or string (representing a float or a percentage) | ||
- relative_tolerance: bool, to explicitly use passed tolerance as relative | ||
|
||
- complex1 : student result (float complex number) | ||
- complex2 : instructor result (float complex number) | ||
- tolerance : string representing a number or float | ||
- relative_tolerance: bool, used when`tolerance` is float to explicitly use passed tolerance as relative. | ||
Note: when a tolerance is a percentage (i.e. '10%'), it will compute that | ||
percentage of the instructor result and yield a number. | ||
|
||
If relative_tolerance is set to False, it will use that value and the | ||
instructor result to define the bounds of valid student result: | ||
instructor_complex = 10, tolerance = '10%' will give [9.0, 11.0]. | ||
|
||
If relative_tolerance is set to True, it will use that value and both | ||
instructor result and student result to define the bounds of valid student | ||
result: | ||
instructor_complex = 10, student_complex = 20, tolerance = '10%' will give | ||
[8.0, 12.0]. | ||
This is typically used internally to compare float, with a | ||
default_tolerance = '0.001%'. | ||
|
||
Default tolerance of 1e-3% is added to compare two floats for | ||
near-equality (to handle machine representation errors). | ||
|
@@ -29,23 +42,28 @@ def compare_with_tolerance(complex1, complex2, tolerance=default_tolerance, rela | |
In [212]: 1.9e24 - 1.9*10**24 | ||
Out[212]: 268435456.0 | ||
""" | ||
if isinstance(tolerance, str): | ||
if tolerance == default_tolerance: | ||
relative_tolerance = True | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As default tolerance ends with '%' it should go through common path for '%' tolerances. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @auraz When something like 100 +- 10% is entered in Studio, we want the tolerance to be absolute (it was relative before and was the cause of the bug). But when default tolerance ('0.001%') is used throughout the code, we want it to be relative. Hence the if clause. |
||
if tolerance.endswith('%'): | ||
tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01 | ||
if not relative_tolerance: | ||
tolerance = tolerance * abs(instructor_complex) | ||
else: | ||
tolerance = evaluator(dict(), dict(), tolerance) | ||
|
||
if relative_tolerance: | ||
tolerance = tolerance * max(abs(complex1), abs(complex2)) | ||
elif tolerance.endswith('%'): | ||
tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01 | ||
tolerance = tolerance * max(abs(complex1), abs(complex2)) | ||
else: | ||
tolerance = evaluator(dict(), dict(), tolerance) | ||
tolerance = tolerance * max(abs(student_complex), abs(instructor_complex)) | ||
|
||
if isinf(complex1) or isinf(complex2): | ||
# If an input is infinite, we can end up with `abs(complex1-complex2)` and | ||
if isinf(student_complex) or isinf(instructor_complex): | ||
# If an input is infinite, we can end up with `abs(student_complex-instructor_complex)` and | ||
# `tolerance` both equal to infinity. Then, below we would have | ||
# `inf <= inf` which is a fail. Instead, compare directly. | ||
return complex1 == complex2 | ||
return student_complex == instructor_complex | ||
else: | ||
# v1 and v2 are, in general, complex numbers: | ||
# there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()). | ||
return abs(complex1 - complex2) <= tolerance | ||
return abs(student_complex - instructor_complex) <= tolerance | ||
|
||
|
||
def contextualize_text(text, context): # private | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have not-a-string-type tolerance anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
O, I've found one place.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In previous code, when tolerance was not ending on '%', it was going through 'evaluator(dict(), dict(), tolerance)'. Now it is going through only if it is string. Why it is so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@auraz Is there any reason to evaluate anything else than a string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not know, It should be investigated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@auraz
compare_with_tolerance
is used in only 1 fileresponsetypes.py
5 times (if we don't count tests). It's value is eitherfloat_info.epsilon
, the default value (0.001%
) or what is specified in the XML. Do you think this might need evaluation? I guess to be on the safe side, we should let it go throughevaluator(dict(), dict(), tolerance)
every time it's not a percentage. Thanks. Will do.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@auraz I take this back, if you pass a float through the evaluator, the following error is raised:
I propose we leave things as is. I tested the code manually and tests all pass. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank you for looking into. I agree with you.