The mpi type holds an interval defined by a pair of mpf values. Arithmetic on intervals uses conservative rounding so that, if an interval is interpreted as a numerical uncertainty interval for a fixed number, any sequence of interval operations will produce an interval that contains what would be the result of applying the same sequence of operations to the exact number.
You can create an mpi from a number (treated as a zero-width interval) or a pair of numbers. Strings are treated as exact decimal numbers (note that a Python float like 0.1 generally does not represent the same number as its literal; use '0.1' instead):
>>> from mpmath import *
>>> mp.dps = 15
>>> mpi(3)
[3.0, 3.0]
>>> mpi(2, 3)
[2.0, 3.0]
>>> mpi(0.1) # probably not what you want
[0.10000000000000000555, 0.10000000000000000555]
>>> mpi('0.1') # good
[0.099999999999999991673, 0.10000000000000000555]
The fact that '0.1' results in an interval of nonzero width proves that 1/10 cannot be represented using binary floating-point numbers at this precision level (in fact, it cannot be represented exactly at any precision).
Some basic examples of interval arithmetic operations are:
>>> mpi(0,1) + 1
[1.0, 2.0]
>>> mpi(0,1) + mpi(4,6)
[4.0, 7.0]
>>> 2 * mpi(2, 3)
[4.0, 6.0]
>>> mpi(-1, 1) * mpi(10, 20)
[-20.0, 20.0]
Intervals have the properties .a, .b (endpoints), .mid, and .delta (width):
>>> x = mpi(2, 5)
>>> x.a
mpf('2.0')
>>> x.b
mpf('5.0')
>>> x.mid
mpf('3.5')
>>> x.delta
mpf('3.0')
Intervals may be infinite or half-infinite:
>>> 1 / mpi(2, inf)
[0.0, 0.5]
The in operator tests whether a number or interval is contained in another interval:
>>> mpi(0, 2) in mpi(0, 10)
True
>>> 3 in mpi(-inf, 0)
False
Division is generally not an exact operation in floating-point arithmetic. Using interval arithmetic, we can track both the error from the division and the error that propagates if we follow up with the inverse operation:
>>> 1 / mpi(3)
[0.33333333333333331483, 0.33333333333333337034]
>>> 1 / (1 / mpi(3))
[2.9999999999999995559, 3.0000000000000004441]
The same goes for computing square roots:
>>> (mpi(2) ** 0.5) ** 2
[1.9999999999999995559, 2.0000000000000004441]
By design, interval arithmetic propagates errors, no matter how tiny, that would get rounded off in normal floating-point arithmetic:
>>> mpi(1) + mpi('1e-10000')
[1.0, 1.000000000000000222]
Interval arithmetic uses the same precision as the mpf class; if mp.dps = 50 is set, all interval operations will be carried out with 50-digit precision. Of course, interval arithmetic is guaranteed to give correct bounds at any precision, but a higher precision makes the intervals narrower and hence more accurate:
>>> mp.dps = 5
>>> mpi(pi)
[3.141590118, 3.141593933]
>>> mp.dps = 30
>>> mpi(pi) # doctest: +ELLIPSIS
[3.14159265358979...793333, 3.14159265358979...797277]
It should be noted that the support for interval arithmetic in mpmath is still somewhat primitive, but the standard arithmetic operators +, -, *, /, as well as integer powers should work correctly. It is not currently possible to use functions like sin or log with interval arguments. You can convert mathematical constants to intervals (as in the previous example) and compute fractional powers, but this is not currently guaranteed to give correct results (although it most likely will).
Interval arithmetic can be used to establish inequalities such as exp(pi*sqrt(163)) < 640320**3 + 744. The left-hand and right-hand sides in this inequality agree to over 30 digits, so low-precision arithmetic may give the wrong result:
>>> mp.dps = 25
>>> exp(pi*sqrt(163)) < (640320**3 + 744)
False
The answer should be True, but the rounding errors are larger than the difference between the numbers. To get the right answer, we can use interval arithmetic to check the sign of the difference between the two sides of the inequality. Interval arithmetic does not tell us the answer right away if we keep mp.dps = 25, but it is honest enough to admit it:
>>> mpi(e) ** (mpi(pi) * mpi(163)**0.5) - (640320**3 + 744)
... # doctest: +ELLIPSIS
[-0.000000793..., 0.000000946...]
There is both a negative and a positive endpoint, so we cannot tell for certain whether the true difference is on one side or the other of zero. The solution is to increase the precision until the answer is strictly one-signed:
>>> mp.dps = 35
>>> mpi(e) ** (mpi(pi) * mpi(163)**0.5) - (640320**3 + 744)
... # doctest: +ELLIPSIS
[-7.499745...e-13, -7.498606...-13]