Fix: bt2: erroneous integer comparison of Field and Value
authorFrancis Deslauriers <francis.deslauriers@efficios.com>
Tue, 25 Jun 2019 21:44:32 +0000 (17:44 -0400)
committerPhilippe Proulx <eeppeliteloop@gmail.com>
Thu, 27 Jun 2019 20:54:53 +0000 (16:54 -0400)
Issue
=====
The `{Unsigned, Signed}IntegerValue` class inherit its __eq__() method
from the `_NumericValue` class. This method converts the `other` object
to a `complex` number to make the comparison as generic as possible. The
issue is that the large complex number comparison is unreliable as it's
using float comparison[1]. The timestamps we are dealing with will often
be going beyond the limit of exact comparison possible with the IEEE 754
double-precision[2].

I encountered this issue while comparing a
`bt2.value.SignedIntegerValue` to a large Python Integer representing a
timestamp in nanosecond.

Here is a showcase of the issue reproduced without the `bt2` bindings:
  In [1]: 42 == complex(42)
  Out[1]: True

  In [2]: 1561489497327275497 == complex(1561489497327275497)
  Out[2]: False

The same issue is reproducible with the `_{Unsigned,
Signed}IntegerField`.

Solution
========
Implement the __eq__() and __lt__() methods in the `_IntegralValue`
class and but still use `_NumericValue`'s methods if the `other` object
is not an instance of `numbers.Integral`.

Implement the __eq__() and __lt__() methods in the `_IntegralField`
class and but still use `_NumericField`'s methods if the `other` object
is not an instance of `numbers.Integral`.

Drawback
========
None.

Note
====
I added test cases to cover large integer comparison for both Integer Value and
integer Field classes.

[1]: https://stackoverflow.com/questions/5595425/what-is-the-best-way-to-compare-floats-for-almost-equality-in-python
[2]: https://en.wikipedia.org/wiki/Double-precision_floating-point_format

Signed-off-by: Francis Deslauriers <francis.deslauriers@efficios.com>
Change-Id: I8448bd42704ad1a2d215bece8534c839b9468f25
Reviewed-on: https://review.lttng.org/c/babeltrace/+/1538
Reviewed-by: Philippe Proulx <eeppeliteloop@gmail.com>
src/bindings/python/bt2/bt2/field.py
src/bindings/python/bt2/bt2/value.py
tests/bindings/python/bt2/test_field.py
tests/bindings/python/bt2/test_value.py

index 99daff5a036a675f090a39c5cbf2029b1704f900..f5b9b59645365bcfcca2079101986fa516109602 100644 (file)
@@ -250,6 +250,18 @@ class _IntegralField(_NumericField, numbers.Integral):
         self.value = self | other
         return self
 
+    def __lt__(self, other):
+        if not isinstance(other, numbers.Integral):
+            return super().__lt__(other);
+
+        return self._value < int(other)
+
+    def _spec_eq(self, other):
+        if not isinstance(other, numbers.Integral):
+            return super()._spec_eq(other);
+
+        return self._value == int(other)
+
 
 class _IntegerField(_IntegralField, _Field):
     pass
index 22623c228f88e2ee21888841ba7f6c5cac465c02..c5c23fe6783ab9ca018da78a752b2126cc85bd70 100644 (file)
@@ -312,6 +312,18 @@ class _IntegralValue(_NumericValue, numbers.Integral):
         self.value = self | other
         return self
 
+    def __lt__(self, other):
+        if not isinstance(other, numbers.Integral):
+            return super().__lt__(other)
+
+        return self._value < int(other)
+
+    def __eq__(self, other):
+        if not isinstance(other, numbers.Integral):
+            return super().__eq__(other)
+
+        return self._value == int(other)
+
 
 class _RealValue(_NumericValue, numbers.Real):
     pass
index 61eeb93337de426a36b2377f1e987536c65130ff..f6fc4ca3037e0229eefa3abbdde5018278a8c8cb 100644 (file)
@@ -836,6 +836,15 @@ class _TestIntegerFieldCommon(_TestNumericField):
         field.value = 1777
         self.assertEqual(field, raw)
 
+    def test_assign_big_uint(self):
+        uint_fc = self._tc.create_unsigned_integer_field_class(64)
+        field = _create_field(self._tc, uint_fc)
+        # Larger than the IEEE 754 double-precision exact representation of
+        # integers.
+        raw = (2**53) + 1
+        field.value = (2**53) + 1
+        self.assertEqual(field, raw)
+
     def test_assign_uint_invalid_neg(self):
         uint_fc = self._tc.create_unsigned_integer_field_class(32)
         field = _create_field(self._tc, uint_fc)
index 51e94895a4b98b250ded4db3020ada31ff0000f4..8f886c7233afb4cc73047f2ee34f15ba244bd35f 100644 (file)
@@ -1031,6 +1031,13 @@ class SignedIntegerValueTestCase(_TestIntegerValue, unittest.TestCase):
         self._def.value = raw
         self.assertEqual(self._def, raw)
 
+    def test_compare_big_int(self):
+        # Larger than the IEEE 754 double-precision exact representation of
+        # integers.
+        raw = (2**53) + 1
+        v = bt2.create_value(raw)
+        self.assertEqual(v, raw)
+
 
 _inject_numeric_testing_methods(SignedIntegerValueTestCase)
 
This page took 0.027572 seconds and 4 git commands to generate.