Repository: mpmath/mpmath (python)

Issue:
Interval arithmetic: incorrect results for min and max
```
Consider the following operation:

a = iv.mpf([0,1])
b = 0.5

min(a,b)

Mpmath returns the range [0,1] though there is no posible value of a that can give a result greater than 0.5

The correct response is [0,0.5]
```

Original issue for #258: http://code.google.com/p/mpmath/issues/detail?id=218
Original author: https://code.google.com/u/117464931809270512527/


Pull request description:
Raise an exception if iv's comparison can't be decided
Closes #258

Patch:
diff --git a/docs/contexts.rst b/docs/contexts.rst
index 77ab898..606def7 100644
--- a/docs/contexts.rst
+++ b/docs/contexts.rst
@@ -144,7 +144,11 @@ Intervals may be infinite or half-infinite::
     >>> print(1 / iv.mpf([2, 'inf']))
     [0.0, 0.5]
 
-The equality testing operators ``==`` and ``!=`` check whether their operands are identical as intervals; that is, have the same endpoints. The ordering operators ``< <= > >=`` permit inequality testing using triple-valued logic: a guaranteed inequality returns ``True`` or ``False`` while an indeterminate inequality returns ``None``::
+The equality testing operators ``==`` and ``!=`` check whether their operands
+are identical as intervals; that is, have the same endpoints. The ordering
+operators ``< <= > >=`` permit inequality testing using triple-valued logic: a
+guaranteed inequality returns ``True`` or ``False`` while an indeterminate
+inequality raises :exc:`ValueError`::
 
     >>> iv.mpf([1,2]) == iv.mpf([1,2])
     True
@@ -156,12 +160,18 @@ The equality testing operators ``==`` and ``!=`` check whether their operands ar
     True
     >>> iv.mpf([1,2]) < 1
     False
-    >>> iv.mpf([1,2]) < 2    # returns None
+    >>> iv.mpf([1,2]) < 2
+    Traceback (most recent call last):
+      ...
+    ValueError
     >>> iv.mpf([2,2]) < 2
     False
     >>> iv.mpf([1,2]) <= iv.mpf([2,3])
     True
-    >>> iv.mpf([1,2]) < iv.mpf([2,3])  # returns None
+    >>> iv.mpf([1,2]) < iv.mpf([2,3])
+    Traceback (most recent call last):
+      ...
+    ValueError
     >>> iv.mpf([1,2]) < iv.mpf([-1,0])
     False
 
@@ -236,13 +246,19 @@ seen by increasing the precision::
     >>> print(mp.exp(mp.pi*mp.sqrt(163)))
     262537412640768743.99999999999925007259719818568888
 
-With interval arithmetic, the comparison returns ``None`` until the precision
-is large enough for `x-y` to have a definite sign::
+With interval arithmetic, the comparison raises :exc:`ValueError` until the
+precision is large enough for `x-y` to have a definite sign::
 
     >>> iv.dps = 15
     >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744)
+    Traceback (most recent call last):
+      ...
+    ValueError
     >>> iv.dps = 30
     >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744)
+    Traceback (most recent call last):
+      ...
+    ValueError
     >>> iv.dps = 60
     >>> iv.exp(iv.pi*iv.sqrt(163)) > (640320**3+744)
     False
diff --git a/mpmath/libmp/libmpi.py b/mpmath/libmp/libmpi.py
index e3d8e70..8aec5f5 100644
--- a/mpmath/libmp/libmpi.py
+++ b/mpmath/libmp/libmpi.py
@@ -37,14 +37,14 @@ def mpi_lt(s, t):
     ta, tb = t
     if mpf_lt(sb, ta): return True
     if mpf_ge(sa, tb): return False
-    return None
+    raise ValueError
 
 def mpi_le(s, t):
     sa, sb = s
     ta, tb = t
     if mpf_le(sb, ta): return True
     if mpf_gt(sa, tb): return False
-    return None
+    raise ValueError
 
 def mpi_gt(s, t): return mpi_lt(t, s)
 def mpi_ge(s, t): return mpi_le(t, s)


Test patch:
diff --git a/mpmath/tests/test_interval.py b/mpmath/tests/test_interval.py
index 93e7f8b..f39a270 100644
--- a/mpmath/tests/test_interval.py
+++ b/mpmath/tests/test_interval.py
@@ -1,4 +1,6 @@
-from mpmath import inf, iv, mp, mpf, mpi, pi, sqrt
+import pytest
+
+from mpmath import inf, iv, mp, mpf, mpi, pi, sqrt, workprec
 
 
 def test_interval_identity():
@@ -303,8 +305,9 @@ def test_interval_complex():
     assert iv.mpc(2,2) ** (-2) == (2+2j) ** (-2)
     assert iv.cos(2).ae(mp.cos(2))
     assert iv.sin(2).ae(mp.sin(2))
-    assert iv.cos(2+3j).ae(mp.cos(2+3j))
-    assert iv.sin(2+3j).ae(mp.sin(2+3j))
+    with workprec(54):
+        assert iv.cos(2+3j).ae(mp.cos(2+3j))
+        assert iv.sin(2+3j).ae(mp.sin(2+3j))
 
 def test_interval_complex_arg():
     assert iv.arg(3) == 0
@@ -351,9 +354,9 @@ def test_interval_complex_arg():
 
 def test_interval_ae():
     x = iv.mpf([1,2])
-    assert x.ae(1) is None
-    assert x.ae(1.5) is None
-    assert x.ae(2) is None
+    pytest.raises(ValueError, lambda: x.ae(1))
+    pytest.raises(ValueError, lambda: x.ae(1.5))
+    pytest.raises(ValueError, lambda: x.ae(2))
     assert x.ae(2.01) is False
     assert x.ae(0.99) is False
     x = iv.mpf(3.5)
@@ -362,8 +365,8 @@ def test_interval_ae():
     assert x.ae(3.5-1e-15) is True
     assert x.ae(3.501) is False
     assert x.ae(3.499) is False
-    assert x.ae(iv.mpf([3.5,3.501])) is None
-    assert x.ae(iv.mpf([3.5,4.5+1e-15])) is None
+    pytest.raises(ValueError, lambda: x.ae(iv.mpf([3.5,3.501])))
+    pytest.raises(ValueError, lambda: x.ae(iv.mpf([3.5,4.5+1e-15])))
 
 def test_interval_nstr():
     iv.dps = n = 30
@@ -439,3 +442,9 @@ def test_interval_conversions():
         assert float(r.b) == float(b)
         assert complex(r.a) == complex(a)
         assert complex(r.b) == complex(b)
+
+def test_issue_258():
+    a = iv.mpf([0, 1])
+    b = 0.5
+    pytest.raises(ValueError, lambda: min(a, b))
+    pytest.raises(ValueError, lambda: max(a, b))