Commits

Anonymous committed 87464db

added new floating point comparisons.

  • Participants
  • Parent commits d9a6840
  • Tags 0.1.3

Comments (0)

Files changed (2)

strainer/almostequal.py

+## {{{ http://code.activestate.com/recipes/577124/ (r1)
+def _float_approx_equal(x, y, tol=1e-18, rel=1e-7):
+    if tol is rel is None:
+        raise TypeError('cannot specify both absolute and relative errors are None')
+    tests = []
+    if tol is not None: tests.append(tol)
+    if rel is not None: tests.append(rel*abs(x))
+    assert tests
+    return abs(x - y) <= max(tests)
+
+
+def approx_equal(x, y, *args, **kwargs):
+    """approx_equal(float1, float2[, tol=1e-18, rel=1e-7]) -> True|False
+    approx_equal(obj1, obj2[, *args, **kwargs]) -> True|False
+
+    Return True if x and y are approximately equal, otherwise False.
+
+    If x and y are floats, return True if y is within either absolute error
+    tol or relative error rel of x. You can disable either the absolute or
+    relative check by passing None as tol or rel (but not both).
+
+    For any other objects, x and y are checked in that order for a method
+    __approx_equal__, and the result of that is returned as a bool. Any
+    optional arguments are passed to the __approx_equal__ method.
+
+    __approx_equal__ can return NotImplemented to signal that it doesn't know
+    how to perform that specific comparison, in which case the other object is
+    checked instead. If neither object have the method, or both defer by
+    returning NotImplemented, approx_equal falls back on the same numeric
+    comparison used for floats.
+
+    >>> almost_equal(1.2345678, 1.2345677)
+    True
+    >>> almost_equal(1.234, 1.235)
+    False
+
+    """
+    if not (type(x) is type(y) is float):
+        # Skip checking for __approx_equal__ in the common case of two floats.
+        methodname = '__approx_equal__'
+        # Allow the objects to specify what they consider "approximately equal",
+        # giving precedence to x. If either object has the appropriate method, we
+        # pass on any optional arguments untouched.
+        for a,b in ((x, y), (y, x)):
+            try:
+                method = getattr(a, methodname)
+            except AttributeError:
+                continue
+            else:
+                result = method(b, *args, **kwargs)
+                if result is NotImplemented:
+                    continue
+                return bool(result)
+    # If we get here without returning, then neither x nor y knows how to do an
+    # approximate equal comparison (or are both floats). Fall back to a numeric
+    # comparison.
+    return _float_approx_equal(x, y, *args, **kwargs)
+## end of http://code.activestate.com/recipes/577124/ }}}

strainer/operators.py

 from pprint import pformat, pprint
 from simplejson import loads
 from nose.tools import *
+from almostequal import approx_equal
 import strainer.log as log
 
 log = log.log
         new_node.tail = ''
     for child in node.getchildren():
         if child is not None:
-            child = remove_whitespace_nodes(child)
+            child = remove_whitespace_nods(child)
         new_node.append(child)
     return new_node
 
     return True
 
 def _eq_list(ca, cb, ignore=None):
-    eq_pprint(len(ca), len(cb), "The lengths of the lists are different %s != %s" % (str(ca), str(cb)))
+    r = eq_pprint(len(ca), len(cb), "The lengths of the lists are different %s != %s" % (str(ca), str(cb)))
+    if not r:
+        return False
     for i, v in enumerate(ca):
         if isinstance(v, dict):
-            _eq_dict(ca[i], cb[i], ignore=ignore)
+            if not _eq_dict(ca[i], cb[i], ignore=ignore):
+                return False
         elif isinstance(v, list):
-            _eq_list(ca[i], cb[i], ignore=ignore)
+            if not _eq_list(ca[i], cb[i], ignore=ignore):
+                return False
         else:
-            eq_pprint(ca[i], cb[i])
+            if not eq_pprint(ca[i], cb[i]):
+                return False
     return True
 
 def _eq_dict(ca, cb, ignore=None):
 
     #this needs to be recursive so we can '&ignore'-out ids anywhere in a json stream
     for key in set(ca.keys() + cb.keys()):
-        assert key in ca, '%s!= %s\n key "%s" not in first argument' %(ca, cb, key)
-        assert key in cb, '%s!= %s\n key "%s" not in second argument' %(ca, cb, key)
+        if key not in ca:
+            log.error('%s!= %s\n key "%s" not in first argument' %(ca, cb, key))
+            return False
+        if key not in cb:
+            log.error('%s!= %s\n key "%s" not in second argument' %(ca, cb, key))
+            return False
+        
         v1 = ca[key]
         v2 = cb[key]
         log.info('Comparing values for key: %s', key)
             log.info('Ignored comparison for key: %s', key)
             continue
         if not isinstance(v2, basestring) and isinstance(v1, basestring):
-            eq_pprint(type(v1), type(v2), 'The types of values for "%s" do not match (%s vs. %s)' %(key, v1, v2))
+            if not eq_pprint(type(v1), type(v2)):
+                log.error('The types of values for "%s" do not match (%s vs. %s)' %(key, v1, v2))
+                return False
         if isinstance(v1, list):
-            _eq_list(v1, v2, ignore=ignore)
+            if not _eq_list(v1, v2, ignore=ignore):
+                return False
         elif isinstance(v1, dict):
-            _eq_dict(v1, v2, ignore=ignore)
+            if not _eq_dict(v1, v2, ignore=ignore):
+                return False
+        elif isinstance(v1, float) and isinstance(v2, float):
+            if not approx_equal(v1, v2):
+                log.error('The values for "%s" do not match (%.30f vs. %.30f)' %(key, v1, v2))
+                return False
         else:
-            eq_pprint(v1, v2, 'The types of values for "%s" do not match (%s vs. %s)' %(key, v1, v2))
+            if not v1 == v2:
+                log.error('The values for "%s" do not match (%s vs. %s)' %(key, v1, v2))
+                return False
     return True
 
 def eq_dict(a, b, ignore=None):
     ca = copy.deepcopy(a)
     cb = copy.deepcopy(b)
                 
-    _eq_dict(ca, cb, ignore=ignore)
-    return True
+    return _eq_dict(ca, cb, ignore=ignore)
 
 def eq_json(a, b):
     if isinstance(a, basestring):