Bläddra i källkod

Added stddev/gmean/gstddev to summary.py

Christopher Haster 3 år sedan
förälder
incheckning
a2fb7089dd
6 ändrade filer med 266 tillägg och 230 borttagningar
  1. 27 29
      scripts/code.py
  2. 38 37
      scripts/coverage.py
  3. 28 29
      scripts/data.py
  4. 27 29
      scripts/stack.py
  5. 27 29
      scripts/struct_.py
  6. 119 77
      scripts/summary.py

+ 27 - 29
scripts/code.py

@@ -31,7 +31,7 @@ TYPE = 'tTrRdD'
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -40,13 +40,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -54,14 +63,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -73,9 +74,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -86,9 +87,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -99,6 +100,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -114,12 +118,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # code size results
 class CodeResult(co.namedtuple('CodeResult', 'file,function,code_size')):
     __slots__ = ()
@@ -285,8 +283,8 @@ def table(results, diff_results=None, *,
                     r.code_size.diff_table()
                         if r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))))
             else:
                 print(' %s %s %s%s' % (
@@ -299,8 +297,8 @@ def table(results, diff_results=None, *,
                         diff_r.code_size if diff_r else None)
                         if r or diff_r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))
                         if ratio else ''))
 
@@ -324,8 +322,8 @@ def table(results, diff_results=None, *,
             r.code_size.diff_table()
                 if r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))))
     else:
         print(' %s %s %s%s' % (
@@ -338,8 +336,8 @@ def table(results, diff_results=None, *,
                 diff_r.code_size if diff_r else None)
                 if r or diff_r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))
                 if ratio else ''))
 

+ 38 - 37
scripts/coverage.py

@@ -32,7 +32,7 @@ GCOV_TOOL = ['gcov']
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -41,13 +41,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -55,14 +64,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -74,9 +75,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -87,9 +88,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -100,6 +101,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -115,16 +119,10 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # fractional fields, a/b
 class FracField(co.namedtuple('FracField', 'a,b')):
     __slots__ = ()
-    def __new__(cls, a, b=None):
+    def __new__(cls, a=0, b=None):
         if isinstance(a, FracField) and b is None:
             return a
         if isinstance(a, str) and b is None:
@@ -136,6 +134,9 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __str__(self):
         return '%s/%s' % (self.a, self.b)
 
+    def __float__(self):
+        return float(self.a)
+
     none = '%11s %7s' % ('-', '-')
     def table(self):
         if not self.b.x:
@@ -144,8 +145,8 @@ class FracField(co.namedtuple('FracField', 'a,b')):
         t = self.a.x/self.b.x
         return '%11s %7s' % (
             self,
-            '∞%' if t == float('+inf')
-            else '-∞%' if t == float('-inf')
+            '∞%' if t == +m.inf
+            else '-∞%' if t == -m.inf
             else '%.1f%%' % (100*t))
 
     diff_none = '%11s' % '-'
@@ -172,12 +173,15 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __add__(self, other):
         return FracField(self.a + other.a, self.b + other.b)
 
+    def __sub__(self, other):
+        return FracField(self.a - other.a, self.b - other.b)
+
     def __mul__(self, other):
         return FracField(self.a * other.a, self.b + other.b)
 
     def __lt__(self, other):
-        self_r = self.a.x/self.b.x if self.b.x else float('-inf')
-        other_r = other.a.x/other.b.x if other.b.x else float('-inf')
+        self_r = self.a.x/self.b.x if self.b.x else -m.inf
+        other_r = other.a.x/other.b.x if other.b.x else -m.inf
         return self_r < other_r
 
     def __gt__(self, other):
@@ -189,9 +193,6 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        return FracField(self.a / n, self.b / n)
-
 # coverage results
 class CoverageResult(co.namedtuple('CoverageResult',
         'file,function,line,'
@@ -416,8 +417,8 @@ def table(results, diff_results=None, *,
                     r.coverage_branches.diff_table()
                         if r else FracField.diff_none,
                     ' (%s)' % ', '.join(
-                            '+∞%' if t == float('+inf')
-                            else '-∞%' if t == float('-inf')
+                            '+∞%' if t == +m.inf
+                            else '-∞%' if t == -m.inf
                             else '%+.1f%%' % (100*t)
                             for t in [line_ratio, branch_ratio])))
             else:
@@ -439,8 +440,8 @@ def table(results, diff_results=None, *,
                         diff_r.coverage_branches if diff_r else None)
                         if r or diff_r else FracField.diff_none,
                     ' (%s)' % ', '.join(
-                            '+∞%' if t == float('+inf')
-                            else '-∞%' if t == float('-inf')
+                            '+∞%' if t == +m.inf
+                            else '-∞%' if t == -m.inf
                             else '%+.1f%%' % (100*t)
                             for t in [line_ratio, branch_ratio]
                             if t)
@@ -473,8 +474,8 @@ def table(results, diff_results=None, *,
             r.coverage_branches.diff_table()
                 if r else FracField.diff_none,
             ' (%s)' % ', '.join(
-                    '+∞%' if t == float('+inf')
-                    else '-∞%' if t == float('-inf')
+                    '+∞%' if t == +m.inf
+                    else '-∞%' if t == -m.inf
                     else '%+.1f%%' % (100*t)
                     for t in [line_ratio, branch_ratio])))
     else:
@@ -496,8 +497,8 @@ def table(results, diff_results=None, *,
                 diff_r.coverage_branches if diff_r else None)
                 if r or diff_r else FracField.diff_none,
             ' (%s)' % ', '.join(
-                    '+∞%' if t == float('+inf')
-                    else '-∞%' if t == float('-inf')
+                    '+∞%' if t == +m.inf
+                    else '-∞%' if t == -m.inf
                     else '%+.1f%%' % (100*t)
                     for t in [line_ratio, branch_ratio]
                     if t)

+ 28 - 29
scripts/data.py

@@ -28,10 +28,11 @@ NM_TOOL = ['nm']
 TYPE = 'dDbB'
 
 
+
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -40,13 +41,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -54,14 +64,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -73,9 +75,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -86,9 +88,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -99,6 +101,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -114,12 +119,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # data size results
 class DataResult(co.namedtuple('DataResult', 'file,function,data_size')):
     __slots__ = ()
@@ -285,8 +284,8 @@ def table(results, diff_results=None, *,
                     r.data_size.diff_table()
                         if r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))))
             else:
                 print(' %s %s %s%s' % (
@@ -299,8 +298,8 @@ def table(results, diff_results=None, *,
                         diff_r.data_size if diff_r else None)
                         if r or diff_r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))
                         if ratio else ''))
 
@@ -324,8 +323,8 @@ def table(results, diff_results=None, *,
             r.data_size.diff_table()
                 if r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))))
     else:
         print(' %s %s %s%s' % (
@@ -338,8 +337,8 @@ def table(results, diff_results=None, *,
                 diff_r.data_size if diff_r else None)
                 if r or diff_r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))
                 if ratio else ''))
 

+ 27 - 29
scripts/stack.py

@@ -25,7 +25,7 @@ CI_PATHS = ['*.ci']
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -34,13 +34,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -48,14 +57,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -67,9 +68,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -80,9 +81,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -93,6 +94,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -108,12 +112,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # size results
 class StackResult(co.namedtuple('StackResult',
         'file,function,stack_frame,stack_limit')):
@@ -394,8 +392,8 @@ def table(results, calls, diff_results=None, *,
                         r.stack_limit.diff_table()
                             if r else IntField.diff_none,
                         ' (%s)' % (
-                            '+∞%' if ratio == float('+inf')
-                            else '-∞%' if ratio == float('-inf')
+                            '+∞%' if ratio == +m.inf
+                            else '-∞%' if ratio == -m.inf
                             else '%+.1f%%' % (100*ratio))))
                 else:
                     print(' %s %s %s %s %s %s%s' % (
@@ -416,8 +414,8 @@ def table(results, calls, diff_results=None, *,
                             diff_r.stack_limit if diff_r else None)
                             if r or diff_r else IntField.diff_none,
                         ' (%s)' % (
-                            '+∞%' if ratio == float('+inf')
-                            else '-∞%' if ratio == float('-inf')
+                            '+∞%' if ratio == +m.inf
+                            else '-∞%' if ratio == -m.inf
                             else '%+.1f%%' % (100*ratio))
                             if ratio else ''))
 
@@ -460,8 +458,8 @@ def table(results, calls, diff_results=None, *,
                 r.stack_limit.diff_table()
                     if r else IntField.diff_none,
                 ' (%s)' % (
-                    '+∞%' if ratio == float('+inf')
-                    else '-∞%' if ratio == float('-inf')
+                    '+∞%' if ratio == +m.inf
+                    else '-∞%' if ratio == -m.inf
                     else '%+.1f%%' % (100*ratio))))
         else:
             print(' %s %s %s %s %s %s%s' % (
@@ -482,8 +480,8 @@ def table(results, calls, diff_results=None, *,
                     diff_r.stack_limit if diff_r else None)
                     if r or diff_r else IntField.diff_none,
                 ' (%s)' % (
-                    '+∞%' if ratio == float('+inf')
-                    else '-∞%' if ratio == float('-inf')
+                    '+∞%' if ratio == +m.inf
+                    else '-∞%' if ratio == -m.inf
                     else '%+.1f%%' % (100*ratio))
                     if ratio else ''))
 

+ 27 - 29
scripts/struct_.py

@@ -27,7 +27,7 @@ OBJDUMP_TOOL = ['objdump']
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -36,13 +36,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -50,14 +59,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -69,9 +70,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -82,9 +83,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -95,6 +96,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -110,12 +114,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # struct size results
 class StructResult(co.namedtuple('StructResult', 'file,struct,struct_size')):
     __slots__ = ()
@@ -328,8 +326,8 @@ def table(results, diff_results=None, *,
                     r.struct_size.diff_table()
                         if r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))))
             else:
                 print(' %s %s %s%s' % (
@@ -342,8 +340,8 @@ def table(results, diff_results=None, *,
                         diff_r.struct_size if diff_r else None)
                         if r or diff_r else IntField.diff_none,
                     ' (%s)' % (
-                        '+∞%' if ratio == float('+inf')
-                        else '-∞%' if ratio == float('-inf')
+                        '+∞%' if ratio == +m.inf
+                        else '-∞%' if ratio == -m.inf
                         else '%+.1f%%' % (100*ratio))
                         if ratio else ''))
 
@@ -367,8 +365,8 @@ def table(results, diff_results=None, *,
             r.struct_size.diff_table()
                 if r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))))
     else:
         print(' %s %s %s%s' % (
@@ -381,8 +379,8 @@ def table(results, diff_results=None, *,
                 diff_r.struct_size if diff_r else None)
                 if r or diff_r else IntField.diff_none,
             ' (%s)' % (
-                '+∞%' if ratio == float('+inf')
-                else '-∞%' if ratio == float('-inf')
+                '+∞%' if ratio == +m.inf
+                else '-∞%' if ratio == -m.inf
                 else '%+.1f%%' % (100*ratio))
                 if ratio else ''))
 

+ 119 - 77
scripts/summary.py

@@ -25,12 +25,26 @@ import re
 CSV_PATHS = ['*.csv']
 
 # supported merge operations
+#
+# this is a terrible way to express these
+#
 OPS = {
-    'add': lambda xs: sum(xs[1:], start=xs[0]),
-    'mul': lambda xs: m.prod(xs[1:], start=xs[0]),
-    'min': min,
-    'max': max,
-    'avg': lambda xs: sum(xs[1:], start=xs[0]) / len(xs),
+    'sum':     lambda xs: sum(xs[1:], start=xs[0]),
+    'prod':    lambda xs: m.prod(xs[1:], start=xs[0]),
+    'min':     min,
+    'max':     max,
+    'mean':    lambda xs: FloatField(sum(float(x) for x in xs) / len(xs)),
+    'stddev':  lambda xs: (
+        lambda mean: FloatField(
+            m.sqrt(sum((float(x) - mean)**2 for x in xs) / len(xs)))
+        )(sum(float(x) for x in xs) / len(xs)),
+    'gmean':   lambda xs: FloatField(m.prod(float(x) for x in xs)**(1/len(xs))),
+    'gstddev': lambda xs: (
+        lambda gmean: FloatField(
+            m.exp(m.sqrt(sum(m.log(float(x)/gmean)**2 for x in xs) / len(xs)))
+            if gmean else m.inf)
+        )(m.prod(float(x) for x in xs)**(1/len(xs)))
+
 }
 
 
@@ -47,7 +61,7 @@ def openio(path, mode='r'):
 # integer fields
 class IntField(co.namedtuple('IntField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0):
         if isinstance(x, IntField):
             return x
         if isinstance(x, str):
@@ -56,13 +70,22 @@ class IntField(co.namedtuple('IntField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, int) or m.isinf(x), x
         return super().__new__(cls, x)
 
+    def __str__(self):
+        if self.x == m.inf:
+            return '∞'
+        elif self.x == -m.inf:
+            return '-∞'
+        else:
+            return str(self.x)
+
     def __int__(self):
         assert not m.isinf(self.x)
         return self.x
@@ -70,14 +93,6 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __float__(self):
         return float(self.x)
 
-    def __str__(self):
-        if self.x == float('inf'):
-            return '∞'
-        elif self.x == float('-inf'):
-            return '-∞'
-        else:
-            return str(self.x)
-
     none = '%7s' % '-'
     def table(self):
         return '%7s' % (self,)
@@ -89,9 +104,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         new = self.x if self else 0
         old = other.x if other else 0
         diff = new - old
-        if diff == float('+inf'):
+        if diff == +m.inf:
             return '%7s' % '+∞'
-        elif diff == float('-inf'):
+        elif diff == -m.inf:
             return '%7s' % '-∞'
         else:
             return '%+7d' % diff
@@ -102,9 +117,9 @@ class IntField(co.namedtuple('IntField', 'x')):
         if m.isinf(new) and m.isinf(old):
             return 0.0
         elif m.isinf(new):
-            return float('+inf')
+            return +m.inf
         elif m.isinf(old):
-            return float('-inf')
+            return -m.inf
         elif not old and not new:
             return 0.0
         elif not old:
@@ -115,6 +130,9 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __add__(self, other):
         return IntField(self.x + other.x)
 
+    def __sub__(self, other):
+        return IntField(self.x - other.x)
+
     def __mul__(self, other):
         return IntField(self.x * other.x)
 
@@ -130,16 +148,10 @@ class IntField(co.namedtuple('IntField', 'x')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return IntField(round(self.x / n))
-
 # float fields
 class FloatField(co.namedtuple('FloatField', 'x')):
     __slots__ = ()
-    def __new__(cls, x):
+    def __new__(cls, x=0.0):
         if isinstance(x, FloatField):
             return x
         if isinstance(x, str):
@@ -148,24 +160,25 @@ class FloatField(co.namedtuple('FloatField', 'x')):
             except ValueError:
                 # also accept +-∞ and +-inf
                 if re.match('^\s*\+?\s*(?:∞|inf)\s*$', x):
-                    x = float('inf')
+                    x = m.inf
                 elif re.match('^\s*-\s*(?:∞|inf)\s*$', x):
-                    x = float('-inf')
+                    x = -m.inf
                 else:
                     raise
+        assert isinstance(x, float), x
         return super().__new__(cls, x)
 
-    def __float__(self):
-        return float(self.x)
-
     def __str__(self):
-        if self.x == float('inf'):
+        if self.x == m.inf:
             return '∞'
-        elif self.x == float('-inf'):
+        elif self.x == -m.inf:
             return '-∞'
         else:
             return '%.1f' % self.x
 
+    def __float__(self):
+        return float(self.x)
+
     none = IntField.none
     table = IntField.table
     diff_none = IntField.diff_none
@@ -173,22 +186,17 @@ class FloatField(co.namedtuple('FloatField', 'x')):
     diff_diff = IntField.diff_diff
     ratio = IntField.ratio
     __add__ = IntField.__add__
+    __sub__ = IntField.__sub__
     __mul__ = IntField.__mul__
     __lt__ = IntField.__lt__
     __gt__ = IntField.__gt__
     __le__ = IntField.__le__
     __ge__ = IntField.__ge__
 
-    def __truediv__(self, n):
-        if m.isinf(self.x):
-            return self
-        else:
-            return FloatField(self.x / n)
-
 # fractional fields, a/b
 class FracField(co.namedtuple('FracField', 'a,b')):
     __slots__ = ()
-    def __new__(cls, a, b=None):
+    def __new__(cls, a=0, b=None):
         if isinstance(a, FracField) and b is None:
             return a
         if isinstance(a, str) and b is None:
@@ -200,6 +208,9 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __str__(self):
         return '%s/%s' % (self.a, self.b)
 
+    def __float__(self):
+        return float(self.a)
+
     none = '%11s %7s' % ('-', '-')
     def table(self):
         if not self.b.x:
@@ -208,8 +219,8 @@ class FracField(co.namedtuple('FracField', 'a,b')):
         t = self.a.x/self.b.x
         return '%11s %7s' % (
             self,
-            '∞%' if t == float('+inf')
-            else '-∞%' if t == float('-inf')
+            '∞%' if t == +m.inf
+            else '-∞%' if t == -m.inf
             else '%.1f%%' % (100*t))
 
     diff_none = '%11s' % '-'
@@ -236,12 +247,15 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __add__(self, other):
         return FracField(self.a + other.a, self.b + other.b)
 
+    def __sub__(self, other):
+        return FracField(self.a - other.a, self.b - other.b)
+
     def __mul__(self, other):
         return FracField(self.a * other.a, self.b + other.b)
 
     def __lt__(self, other):
-        self_r = self.a.x/self.b.x if self.b.x else float('-inf')
-        other_r = other.a.x/other.b.x if other.b.x else float('-inf')
+        self_r = self.a.x/self.b.x if self.b.x else -m.inf
+        other_r = other.a.x/other.b.x if other.b.x else -m.inf
         return self_r < other_r
 
     def __gt__(self, other):
@@ -253,9 +267,6 @@ class FracField(co.namedtuple('FracField', 'a,b')):
     def __ge__(self, other):
         return not self.__lt__(other)
 
-    def __truediv__(self, n):
-        return FracField(self.a / n, self.b / n)
-
 # available types
 TYPES = [IntField, FloatField, FracField]
 
@@ -314,7 +325,7 @@ def homogenize(results, *,
                 if k is not None
                     and k not in fields
                     and not any(k == old_k for _, old_k in renames))
-        by = list(by.keys()) 
+        by = list(by.keys())
 
     # go ahead and clean up none values, these can have a few forms
     results_ = []
@@ -357,6 +368,7 @@ def homogenize(results, *,
 def fold(results, *,
         by=[],
         fields=[],
+        types=None,
         ops={},
         **_):
     folding = co.OrderedDict()
@@ -375,7 +387,7 @@ def fold(results, *,
         for k, vs in r.items():
             if vs:
                 # sum fields by default
-                op = OPS[ops.get(k, 'add')]
+                op = OPS[ops.get(k, 'sum')]
                 r_[k] = op(vs)
 
         # drop any rows without fields and any empty keys
@@ -384,14 +396,24 @@ def fold(results, *,
                 {k: v for k, v in zip(by, name) if v},
                 **r_))
 
-    return folded
+    # what is the type of merged fields?
+    if types is not None:
+        types_ = {}
+        for k in fields:
+            op = OPS[ops.get(k, 'sum')]
+            types_[k] = op([types[k]()]).__class__
 
+    if types is None:
+        return folded
+    else:
+        return types_, folded
 
-def table(results, diff_results=None, *,
-        by=None,
-        fields=None,
-        types=None,
-        ops=None,
+
+def table(results, total, diff_results=None, diff_total=None, *,
+        by=[],
+        fields=[],
+        types={},
+        ops={},
         sort=None,
         reverse_sort=None,
         summary=False,
@@ -472,8 +494,8 @@ def table(results, diff_results=None, *,
                         if k in r else types[k].diff_none
                         for k in fields),
                     ' (%s)' % ', '.join(
-                            '+∞%' if t == float('+inf')
-                            else '-∞%' if t == float('-inf')
+                            '+∞%' if t == +m.inf
+                            else '-∞%' if t == -m.inf
                             else '%+.1f%%' % (100*t)
                             for t in ratios)))
             else:
@@ -488,19 +510,17 @@ def table(results, diff_results=None, *,
                         if k in r or k in diff_r else types[k].diff_none
                         for k in fields),
                     ' (%s)' % ', '.join(
-                            '+∞%' if t == float('+inf')
-                            else '-∞%' if t == float('-inf')
+                            '+∞%' if t == +m.inf
+                            else '-∞%' if t == -m.inf
                             else '%+.1f%%' % (100*t)
                             for t in ratios
                             if t)
                         if any(ratios) else ''))
 
     # print total
-    total = fold(results, by=[], fields=fields, ops=ops)
-    r = total[0] if total else {}
-    if diff_results is not None:
-        diff_total = fold(diff_results, by=[], fields=fields, ops=ops)
-        diff_r = diff_total[0] if diff_total else {}
+    r = total
+    if diff_total is not None:
+        diff_r = diff_total
         ratios = [types[k].ratio(r.get(k), diff_r.get(k))
             for k in fields]
 
@@ -516,8 +536,8 @@ def table(results, diff_results=None, *,
                 if k in r else types[k].diff_none
                 for k in fields),
             ' (%s)' % ', '.join(
-                    '+∞%' if t == float('+inf')
-                    else '-∞%' if t == float('-inf')
+                    '+∞%' if t == +m.inf
+                    else '-∞%' if t == -m.inf
                     else '%+.1f%%' % (100*t)
                     for t in ratios)))
     else:
@@ -532,8 +552,8 @@ def table(results, diff_results=None, *,
                 if k in r or k in diff_r else types[k].diff_none
                 for k in fields),
             ' (%s)' % ', '.join(
-                    '+∞%' if t == float('+inf')
-                    else '-∞%' if t == float('-inf')
+                    '+∞%' if t == +m.inf
+                    else '-∞%' if t == -m.inf
                     else '%+.1f%%' % (100*t)
                     for t in ratios
                     if t)
@@ -597,9 +617,13 @@ def main(csv_paths, *,
     by, fields, types, results = homogenize(results,
         by=by, fields=fields, renames=renames, define=define)
 
+    # fold for total, note we do this with the raw data to avoid
+    # issues with lossy operations
+    total = fold(results, fields=fields, ops=ops)
+    total = total[0] if total else {}
+
     # fold to remove duplicates
-    results = fold(results,
-        by=by, fields=fields, ops=ops)
+    types_, results = fold(results, by=by, fields=fields, types=types, ops=ops)
 
     # write results to CSV
     if args.get('output'):
@@ -624,19 +648,25 @@ def main(csv_paths, *,
         _, _, _, diff_results = homogenize(diff_results,
             by=by, fields=fields, renames=renames, define=define, types=types)
 
+        # fold for total, note we do this with the raw data to avoid
+        # issues with lossy operations
+        diff_total = fold(diff_results, fields=fields, ops=ops)
+        diff_total = diff_total[0] if diff_total else {}
+
         # fold to remove duplicates
-        diff_results = fold(diff_results,
-            by=by, fields=fields, ops=ops)
+        diff_results = fold(diff_results, by=by, fields=fields, ops=ops)
 
     # print table
     if not args.get('quiet'):
         table(
             results,
+            total,
             diff_results if args.get('diff') else None,
+            diff_total if args.get('diff') else None,
             by=by,
             fields=fields,
+            types=types_,
             ops=ops,
-            types=types,
             **args)
 
 
@@ -685,11 +715,11 @@ if __name__ == "__main__":
         help="Only include rows where this field is this value. May include "
             "comma-separated options.")
     parser.add_argument(
-        '--add',
+        '--sum',
         action='append',
         help="Add these fields (the default).")
     parser.add_argument(
-        '--mul',
+        '--prod',
         action='append',
         help="Multiply these fields.")
     parser.add_argument(
@@ -701,9 +731,21 @@ if __name__ == "__main__":
         action='append',
         help="Take the maximum of these fields.")
     parser.add_argument(
-        '--avg',
+        '--mean',
         action='append',
         help="Average these fields.")
+    parser.add_argument(
+        '--stddev',
+        action='append',
+        help="Find the standard deviation of these fields.")
+    parser.add_argument(
+        '--gmean',
+        action='append',
+        help="Find the geometric mean of these fields.")
+    parser.add_argument(
+        '--gstddev',
+        action='append',
+        help="Find the geometric standard deviation of these fields.")
     parser.add_argument(
         '-s', '--sort',
         action='append',