diff --git a/README.md b/README.md
index dae1df22e109aaea41b09ac09db75d9cee013325..47c74b943b4af53f738d84a5cf75a7d084065e78 100644
--- a/README.md
+++ b/README.md
@@ -137,36 +137,35 @@ isinstance(series, DiscreteSeries)
 By calling `as_series()` you get a new DiscreteSeries instance returned.
 
 
-## Ranges
+## Intervals
 
-Can be imported from _sai.ranges_.
+Can be imported from _sai.intervals_.
 
-Range would have been better called an **interval**. It is a continuous subset
-of the real number line.
+Interval is a continuous subset of the real number line.
 
-You can create Ranges as follows:
+You can create Intervals as follows:
 
 ```python
-Range(-5, 5, True, False) == Range('<-5;5)')
+Interval(-5, 5, True, False) == Interval('<-5;5)')
 ```
 
-For more information [use the source](firanka/ranges.py#L33)
-Range's are immutable and hashable. They can be sliced:
+For more information [use the source](firanka/intervals.py#L33)
+Interval's are immutable and hashable. They can be sliced:
 
 ```python
-Range('<-5;5>')[0:] == Range('<0;5>')
+Interval('<-5;5>')[0:] == Interval('<0;5>')
 ```
 
 Slices work as a both-sides-closed range if both sides are shown!
 
-You can check whether a range contains a point
+You can check whether an interval contains a point
 
 ```python
-5 not in Range('<-1;5)')
+5 not in Interval('<-1;5)')
 ```
 
 Or you can check for strict inclusion
 
 ```python
-Range('<-1;1>') in Range('<-2;2>')
+Interval('<-1;1>') in Interval('<-2;2>')
 ```
diff --git a/firanka/__init__.py b/firanka/__init__.py
index 13b708912bf22a449e7551a97fff687b0e4e19f3..e6d0c4f459e59ecdc1cfced4476692dabc323abc 100644
--- a/firanka/__init__.py
+++ b/firanka/__init__.py
@@ -1 +1 @@
-__version__ = '0.1.11'
+__version__ = '0.1.12'
diff --git a/firanka/builders.py b/firanka/builders.py
index d01d1d44c6df6e7273ba09c6fb928b2c682a7c5e..028ea68b8d68be841feef014c7373b5b7e10d72c 100644
--- a/firanka/builders.py
+++ b/firanka/builders.py
@@ -5,7 +5,7 @@ import copy
 
 from sortedcontainers import SortedList
 
-from .ranges import Range
+from .intervals import Interval
 from .series import DiscreteSeries
 
 """
@@ -34,9 +34,9 @@ class DiscreteSeriesBuilder(object):
 
         if index not in self.domain:
             if index <= self.domain.start:
-                self.domain = Range(index, self.domain.stop, True, self.domain.right_inc)
+                self.domain = Interval(index, self.domain.stop, True, self.domain.right_inc)
             if index >= self.domain.stop:
-                self.domain = Range(self.domain.start, index, self.domain.left_inc, True)
+                self.domain = Interval(self.domain.start, index, self.domain.left_inc, True)
 
         self.new_data[index] = value
 
diff --git a/firanka/ranges.py b/firanka/intervals.py
similarity index 77%
rename from firanka/ranges.py
rename to firanka/intervals.py
index b0554d43099c472a6a52bd85134f7dfca9fbe6ae..1ec6354447d773efd3e13b0712781ebfd08967b8 100644
--- a/firanka/ranges.py
+++ b/firanka/intervals.py
@@ -6,34 +6,45 @@ import math
 import six
 
 __all__ = [
-    'Range',
+    'Interval',
     'REAL_SET',
     'EMPTY_SET'
 ]
 
 
-def _pre_range(fun):  # for making sure that first argument gets parsed as a Range
+def _pre_range(fun):  # for making sure that first argument gets parsed as a Interval
     @six.wraps(fun)
     def inner(self, arg, *args, **kwargs):
-        if not isinstance(arg, Range):
-            arg = Range(arg)
+        if not isinstance(arg, Interval):
+            arg = Interval(arg)
         return fun(self, arg, *args, **kwargs)
 
     return inner
 
 
-class Range(object):
+class Interval(object):
     """
-    Range of real numbers. Immutable.
+    Interval of real numbers. Immutable.
     """
     __slots__ = ('start', 'stop', 'left_inc', 'right_inc')
 
+    @_pre_range
+    def __add__(self, other):
+        if self.start > other.start:
+            return other.__add__(self)
+
+        assert not (self.start > other.start)
+
+        if (self.stop == other.start) and (self.right_inc or other.left_inc):
+            return Interval(self.start, other.stop, self.right_inc, other.left_inc)
+
+
     def translate(self, x):
         if x == 0:
             return self
         else:
-            return Range(self.start + x, self.stop + x, self.left_inc,
-                         self.right_inc)
+            return Interval(self.start + x, self.stop + x, self.left_inc,
+                            self.right_inc)
 
     def __fromslice(self, rs):
         start = float('-inf') if rs.start is None else rs.start
@@ -55,7 +66,7 @@ class Range(object):
     def __getargs(self, args):
         if len(args) == 1:
             rs, = args
-            if isinstance(rs, Range):
+            if isinstance(rs, Interval):
                 args = self.__fromrange(rs)
             elif isinstance(rs, slice):
                 args = self.__fromslice(rs)
@@ -71,10 +82,10 @@ class Range(object):
         """
         Create like:
 
-        * Range('<a;b>')
-        * Range(a, b, is_left_closed_, is_right_closed)
-        * Range(a, b) - will have both sides closed, unless one is inf
-        * Range(slice(a, b)) - will have both sides closed, unless one is None
+        * Interval('<a;b>')
+        * Interval(a, b, is_left_closed_, is_right_closed)
+        * Interval(a, b) - will have both sides closed, unless one is inf
+        * Interval(slice(a, b)) - will have both sides closed, unless one is None
 
         :param args:
         """
@@ -90,12 +101,12 @@ class Range(object):
 
     def __contains__(self, x):
         """
-        :type x: index or a Range
+        :type x: index or a Interval
         """
         if isinstance(x, six.string_types):
-            x = Range(x)
+            x = Interval(x)
 
-        if isinstance(x, Range):
+        if isinstance(x, Interval):
             if ((x.start == self.start) and (x.left_inc ^ self.left_inc)) \
                     or ((x.stop == self.stop) and (x.right_inc ^ self.right_inc)):
                 return False
@@ -118,7 +129,7 @@ class Range(object):
         return self.stop - self.start
 
     def __repr__(self):
-        return 'Range(%s, %s, %s, %s)' % (
+        return 'Interval(%s, %s, %s, %s)' % (
             repr(self.start), repr(self.stop), repr(self.left_inc),
             repr(self.right_inc))
 
@@ -126,7 +137,7 @@ class Range(object):
         if not isinstance(item, slice):
             raise ValueError('must be a slice')
 
-        return self.intersection(Range(item))
+        return self.intersection(Interval(item))
 
     def __str__(self):
         return '%s%s;%s%s' % (
@@ -164,7 +175,7 @@ class Range(object):
             stop = p.stop
             right_inc = p.right_inc and (stop in q)
 
-        return Range(start, stop, left_inc, right_inc)
+        return Interval(start, stop, left_inc, right_inc)
 
     @_pre_range
     def __eq__(self, other):
@@ -177,5 +188,5 @@ class Range(object):
         return hash(self.start) ^ hash(self.stop)
 
 
-EMPTY_SET = Range(0, 0, False, False)
-REAL_SET = Range(float('-inf'), float('+inf'), False, False)
+EMPTY_SET = Interval(0, 0, False, False)
+REAL_SET = Interval(float('-inf'), float('+inf'), False, False)
diff --git a/firanka/series/base.py b/firanka/series/base.py
index 5be4ebe08b758bf818d011bc5645c0b90e2bd568..2f812f2bd98d29b6a455a9a62d3aa5d922052d62 100644
--- a/firanka/series/base.py
+++ b/firanka/series/base.py
@@ -6,7 +6,7 @@ import inspect
 from sortedcontainers import SortedList
 
 from firanka.exceptions import NotInDomainError
-from firanka.ranges import Range, EMPTY_SET
+from firanka.intervals import Interval, EMPTY_SET
 
 
 def _has_arguments(fun, n):  # used only in assert clauses
@@ -23,21 +23,21 @@ class Series(object):
     """
 
     def __init__(self, domain, comment=u''):
-        if not isinstance(domain, Range):
-            domain = Range(domain)
+        if not isinstance(domain, Interval):
+            domain = Interval(domain)
         self.domain = domain
         self.comment = comment
 
     def __getitem__(self, item):
         """
         Return a value for given index, or a subslice of this series
-        :param item: a float, or a slice, or a Range
+        :param item: a float, or a slice, or a Interval
         :return: Series instance or a value
         :raises NotInDomainError: index not in domain
         """
-        if isinstance(item, (Range, slice)):
+        if isinstance(item, (Interval, slice)):
             if isinstance(item, slice):
-                item = Range(item)
+                item = Interval(item)
 
             if item not in self.domain:
                 raise NotInDomainError('slicing beyond series domain')
@@ -80,7 +80,7 @@ class Series(object):
 
         points = list(sorted(points))
 
-        domain = domain or Range(points[0], points[-1], True, True)
+        domain = domain or Interval(points[0], points[-1], True, True)
 
         if domain not in self.domain:
             raise NotInDomainError('points not inside this series!')
@@ -120,7 +120,7 @@ class DiscreteSeries(Series):
         if len(data) == 0:
             domain = EMPTY_SET
         elif domain is None:
-            domain = Range(data[0][0], data[-1][0], True, True)
+            domain = Interval(data[0][0], data[-1][0], True, True)
 
         self.data = data
         super(DiscreteSeries, self).__init__(domain, *args, **kwargs)
diff --git a/firanka/series/modulo.py b/firanka/series/modulo.py
index e0063c69e14fd9c1ed50a1d7ea3c017bae6ca4c1..ccd0b4e0bf3e21baee860b93ae633082c0d1ceb7 100644
--- a/firanka/series/modulo.py
+++ b/firanka/series/modulo.py
@@ -4,7 +4,7 @@ from __future__ import print_function, absolute_import, division
 import math
 
 from .base import Series
-from ..ranges import REAL_SET
+from ..intervals import REAL_SET
 
 
 class ModuloSeries(Series):
diff --git a/tests/test_range.py b/tests/test_range.py
index fa286dcaffff2d0596fcb20b5f0772650ab7015e..467d2c996896e2308282e03d8c41bf37740c9222 100644
--- a/tests/test_range.py
+++ b/tests/test_range.py
@@ -3,31 +3,31 @@ from __future__ import print_function, absolute_import, division
 
 import unittest
 
-from firanka.ranges import Range
+from firanka.intervals import Interval
 
 
 class TestRange(unittest.TestCase):
     def do_intersect(self, a, b, val):
         if type(val) == bool:
-            p = Range(a).intersection(b)
+            p = Interval(a).intersection(b)
             if p.is_empty() != val:
                 self.fail(
-                    '%s ^ %s [=> %s] != %s' % (Range(a), Range(b), p, val))
-            p = Range(b).intersection(a)
+                    '%s ^ %s [=> %s] != %s' % (Interval(a), Interval(b), p, val))
+            p = Interval(b).intersection(a)
             if p.is_empty() != val:
                 self.fail(
-                    '%s ^ %s [=> %s] != %s' % (Range(b), Range(a), p, val))
+                    '%s ^ %s [=> %s] != %s' % (Interval(b), Interval(a), p, val))
         else:
-            self.assertEqual(Range(a).intersection(b), Range(val))
-            self.assertEqual(Range(b).intersection(a), Range(val))
+            self.assertEqual(Interval(a).intersection(b), Interval(val))
+            self.assertEqual(Interval(b).intersection(a), Interval(val))
 
     def test_slicing(self):
-        self.assertTrue(Range('<-5;5>')[0:] == Range('<0;5>'))
+        self.assertTrue(Interval('<-5;5>')[0:] == Interval('<0;5>'))
 
     def test_isempty(self):
-        self.assertTrue(Range(-1, -1, False, False).is_empty())
-        self.assertFalse(Range(-1, -1, False, True).is_empty())
-        self.assertEqual(Range(0, 0, False, False), Range(2, 2, False, False))
+        self.assertTrue(Interval(-1, -1, False, False).is_empty())
+        self.assertFalse(Interval(-1, -1, False, True).is_empty())
+        self.assertEqual(Interval(0, 0, False, False), Interval(2, 2, False, False))
 
     def test_intersection(self):
         self.do_intersect('<-10;1>', '<2;3>', True)
@@ -38,31 +38,31 @@ class TestRange(unittest.TestCase):
         self.do_intersect('<-5;5>', '(-5;5)', '(-5;5)')
 
     def test_str_and_repr_and_bool(self):
-        p = Range(-1, 1, True, True)
+        p = Interval(-1, 1, True, True)
         self.assertEqual(eval(repr(p)), p)
-        self.assertEqual(str(Range(-1, 1)), '<-1;1>')
+        self.assertEqual(str(Interval(-1, 1)), '<-1;1>')
 
     def test_constructor(self):
-        self.assertRaises(ValueError, lambda: Range('#2;3>'))
-        self.assertRaises(ValueError, lambda: Range('(2;3!'))
-        self.assertRaises(ValueError, lambda: Range('<-inf;3)'))
-        self.assertEqual(Range(1, 2, True, False), Range('<1;2)'))
-        self.assertEqual(Range(1, 2, True, False), '<1;2)')
+        self.assertRaises(ValueError, lambda: Interval('#2;3>'))
+        self.assertRaises(ValueError, lambda: Interval('(2;3!'))
+        self.assertRaises(ValueError, lambda: Interval('<-inf;3)'))
+        self.assertEqual(Interval(1, 2, True, False), Interval('<1;2)'))
+        self.assertEqual(Interval(1, 2, True, False), '<1;2)')
 
-        r = Range(1, 2, True, False)
-        self.assertEqual(r, Range(r))
+        r = Interval(1, 2, True, False)
+        self.assertEqual(r, Interval(r))
 
     def test_contains(self):
-        self.assertFalse(-1 in Range('<-10;-1)'))
-        self.assertTrue(-10 in Range('<-10;-1)'))
-        self.assertFalse(-10 in Range('(-10;-1>'))
-        self.assertTrue(-1 in Range('(-10;-1>'))
-        self.assertTrue(-5 in Range('(-10;-1>'))
-        self.assertFalse(-20 in Range('(-10;-1>'))
-        self.assertFalse(1 in Range('(-10;-1>'))
-        self.assertFalse('<-10;-1>' in Range('(-10;-1)'))
-        self.assertFalse('<-10;-1)' in Range('(-10;-1>'))
+        self.assertFalse(-1 in Interval('<-10;-1)'))
+        self.assertTrue(-10 in Interval('<-10;-1)'))
+        self.assertFalse(-10 in Interval('(-10;-1>'))
+        self.assertTrue(-1 in Interval('(-10;-1>'))
+        self.assertTrue(-5 in Interval('(-10;-1>'))
+        self.assertFalse(-20 in Interval('(-10;-1>'))
+        self.assertFalse(1 in Interval('(-10;-1>'))
+        self.assertFalse('<-10;-1>' in Interval('(-10;-1)'))
+        self.assertFalse('<-10;-1)' in Interval('(-10;-1>'))
 
-        self.assertTrue(Range('<-5;5>') in Range('<-10;10>'))
-        self.assertTrue('(-1;6)' in Range(-10.0, 10.0, True, False))
-        self.assertTrue('<0.5;1.5>' in Range('<0;2>'))
+        self.assertTrue(Interval('<-5;5>') in Interval('<-10;10>'))
+        self.assertTrue('(-1;6)' in Interval(-10.0, 10.0, True, False))
+        self.assertTrue('<0.5;1.5>' in Interval('<0;2>'))
diff --git a/tests/test_series.py b/tests/test_series.py
index d35862a8a5ff107a0e740a6cd348fc50807613b7..0329e8c206ed6dd52facaa57116305238369810a 100644
--- a/tests/test_series.py
+++ b/tests/test_series.py
@@ -5,7 +5,7 @@ import math
 import unittest
 
 from firanka.exceptions import NotInDomainError
-from firanka.ranges import Range
+from firanka.intervals import Interval
 from firanka.series import DiscreteSeries, FunctionSeries, ModuloSeries, \
     LinearInterpolationSeries, Series
 
@@ -42,7 +42,7 @@ class TestDiscreteSeries(unittest.TestCase):
         self.assertRaises(NotInDomainError, lambda: s[2.5])
 
         s = DiscreteSeries([[0, 0], [1, 1], [2, 2]],
-                           domain=Range(0, 3, True, True))
+                           domain=Interval(0, 3, True, True))
         self.assertEqual(s[0], 0)
         self.assertEqual(s[0.5], 0)
         self.assertEqual(s[1], 1)
@@ -120,7 +120,7 @@ class TestDiscreteSeries(unittest.TestCase):
         EPTS = [x * x ** 2 for x in PTS]
 
         self.assertEqual(sc.eval_points(PTS), EPTS)
-        self.assertTrue(Range('<0;2)') in sc.domain)
+        self.assertTrue(Interval('<0;2)') in sc.domain)
 
     def test_discretize(self):
         # note the invalid data for covering this domain