In [1]:
using Pnums
The 3 bit Pnums contain the values -1, 0, 1, /0 (Gustafson's notation for Infinity), and the open intervals between these values.
In [2]:
[x for x in eachpnum(Pnum3)]
Out[2]:
The corresponding Pbounds represent contiguous intervals with Pnums as endpoints:
In [3]:
Pbound(pn3"0", pn3"1")
Out[3]:
In this case, the endpoints are exact values, and so the Pbound is a closed interval. Using an inexact pnum as an endpoint produces an interval with an open endpoint. One or both endpoints can be open.
In [4]:
[
Pbound(pn3"(0, 1)", pn3"1"),
Pbound(pn3"0", pn3"(0, 1)"),
Pbound(pn3"(0, 1)")
]
Out[4]:
A special feature of Unums 2.0 (and Pnums) is that intervals can span infinity. This is represented by an interval with a left endpoint that is smaller than its right endpoint, using the convention that intervals always move counter-clockwise around the projective circle from their left endpoint to their right endpoint
In [5]:
Pbound3(1, -1)
Out[5]:
In [6]:
pn3"/0" in pb3"[1, -1]"
Out[6]:
In [7]:
pn3"0" in pb3"[1, -1]"
Out[7]:
Pbounds may also contain the entire projective circle, or be empty (each Pbound stores a flag internally that determines whether it is empty).
In [8]:
[
pb3"everything"
pb3"empty"
]
Out[8]:
It's nice to be able to represent the difference between "empty" and "everything". Floating point numbers use NaN in both cases.
In [9]:
sqrt(pn3"-1")
Out[9]:
In [10]:
pn3"0"/pn3"0"
Out[10]:
(the rationale for 0/0 = everything
is that any real number x is a solution to 0*x = 0
)
Having "empty" instead of NaN allows us to get sensible answers in problems where part of the input lies outside the domain of a function:
In [11]:
sqrt(pb3"(-1, 1)")
Out[11]:
Notice that in this case, the input had open endpoints, but the output has 1 closed endpoint and 1 open endpoint.
Arithmetic operations between Pnums produce Pbounds, because arithmetic on inexact Pnums can produce intervals that span multiple Pnums.
In [12]:
2*pn3"(0, 1)"
Out[12]:
Arithmetic is closed on Pbounds, including dividing by bounds that contain 0.
In [13]:
1/pb3"(-1, 1)"
Out[13]:
This is probably the best thing about working with projective reals. Traditional intervals would have to report [-Inf, Inf] for this calculation.
In [14]:
let x = 1/pb3"(-1, 1)"
0.5 in x, 2 in x
end
Out[14]:
Finding (both) square roots of 2:
In [24]:
findroots(x->x^2-2, pb16"everything")
Out[24]:
In [25]:
findroots(x->x^2-2, pb16"everything") |> Pnums.print_decimal
Notice that this is global search:
In [16]:
findroots(x->1/x, pb8"everything")
Out[16]:
We can distinguish poles from roots, and deal with intermediate infinities
In [17]:
findroots(x->1/(1/(x-1/2)+2), pb8"everything")
Out[17]:
Note that the "dependency" problem can damage our ability to find roots precisely
In [18]:
findroots(x->(x+1)*(x-2), pb8"everything")
Out[18]:
In [26]:
findroots(x->x^2 - x - 2, pb8"everything") |> Pnums.print_decimal
The last solution shows up because /0 - /0 = everything
, so we can't rule out infinity as a solution in many problems involving addition or subtraction.
In [20]:
pn8"/0" - pn8"/0"
Out[20]:
In [21]:
findmaximum(x->4-(x-3)^2, pb8"everything")
Out[21]:
Another annoying aspect of the dependency problem is that it can cause solution sets to split into many disjoint pieces
In [27]:
findmaximum(x->x-exp(x), pb8"everything") |> Pnums.print_decimal
Increasing precision reduces the range of answers, but exacerbates the broken-up-set problem:
In [29]:
findmaximum(x->x-exp(x), pb16"everything") |> Pnums.print_decimal
In [ ]: