Introduction to Pnums


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]:
8-element Array{Union{Int64,Pnums.Pbound{Pnums.Pnum3},Pnums.Pnum3},1}:
 pn3"(/0, -1)"
       pn3"-1"
  pn3"(-1, 0)"
        pn3"0"
   pn3"(0, 1)"
        pn3"1"
  pn3"(1, /0)"
       pn3"/0"

The corresponding Pbounds represent contiguous intervals with Pnums as endpoints:


In [3]:
Pbound(pn3"0", pn3"1")


Out[3]:
pb3"[0, 1]"

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]:
3-element Array{Pnums.Pbound{Pnums.Pnum3},1}:
 pb3"(0, 1]"
 pb3"[0, 1)"
 pb3"(0, 1)"

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]:
pb3"[1, -1]"

In [6]:
pn3"/0" in pb3"[1, -1]"


Out[6]:
true

In [7]:
pn3"0" in pb3"[1, -1]"


Out[7]:
false

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]:
2-element Array{Pnums.Pbound{Pnums.Pnum3},1}:
 pb3"everything"
      pb3"empty"

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]:
pb3"empty"

In [10]:
pn3"0"/pn3"0"


Out[10]:
pb3"everything"

(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]:
pb3"[0, 1)"

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]:
pb3"(0, /0)"

Arithmetic is closed on Pbounds, including dividing by bounds that contain 0.


In [13]:
1/pb3"(-1, 1)"


Out[13]:
pb3"(1, -1)"

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]:
(false,true)

Zeros and extrema

Finding (both) square roots of 2:


In [24]:
findroots(x->x^2-2, pb16"everything")


Out[24]:
2-element Array{Pnums.Pbound{Pnums.Pnum16},1}:
 pb16"(-363/256, -181/128)"
   pb16"(181/128, 363/256)"

In [25]:
findroots(x->x^2-2, pb16"everything") |> Pnums.print_decimal


(-1.4179688, -1.4140625)
(1.4140625, 1.4179688)

Notice that this is global search:


In [16]:
findroots(x->1/x, pb8"everything")


Out[16]:
1-element Array{Pnums.Pbound{Pnums.Pnum8},1}:
 pb8"/0"

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]:
1-element Array{Pnums.Pbound{Pnums.Pnum8},1}:
 pb8"/2"

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]:
2-element Array{Pnums.Pbound{Pnums.Pnum8},1}:
 pb8"-1"
  pb8"2"

In [26]:
findroots(x->x^2 - x - 2, pb8"everything") |> Pnums.print_decimal


-1.0
(1.75, 2.5)
(192.0, Inf]

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]:
pb8"everything"

In [21]:
findmaximum(x->4-(x-3)^2, pb8"everything")


Out[21]:
1-element Array{Pnums.Pbound{Pnums.Pnum8},1}:
 pb8"3"

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


(-0.5, -0.4)
(-0.33333334, -0.2)
(-0.2, 0.25)
(0.25, 0.5)
(0.5, 0.5714286)
(0.6666667, 0.8)
(192.0, Inf]

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


(-0.082474224, -0.08226221)
(-0.07920792, -0.07881773)
(-0.07582939, -0.07511737)
(-0.07256236, -0.07158837)
(-0.06911447, -0.067940556)
(-0.06570842, -0.064257026)
(-0.0625, -0.060606062)
(-0.05882353, -0.0569395)
(-0.055363324, -0.053156145)
(-0.051948052, -0.049382716)
(-0.04833837, -0.045714285)
(-0.04481793, -0.04199475)
(-0.041237112, -0.03827751)
(-0.037647057, -0.03448276)
(-0.033970278, -0.03076923)
(-0.030303031, -0.026936026)
(-0.026666667, -0.023121387)
(-0.022922637, -0.019323671)
(-0.019184653, -0.015503876)
(-0.015384615, -0.011627907)
(-0.011594203, -0.007782101)
(-0.007751938, -0.0038910506)
(-0.0038910506, 0.00390625)
(0.00390625, 0.0078125)
(0.0078125, 0.011661808)
(0.0116959065, 0.015564202)
(0.015625, 0.01937046)
(0.019512195, 0.023188407)
(0.023391813, 0.027027028)
(0.027303753, 0.03088803)
(0.03125, 0.034557234)
(0.03508772, 0.038369305)
(0.03902439, 0.042105265)
(0.042895444, 0.045845274)
(0.046783626, 0.049535602)
(0.050632913, 0.053333335)
(0.054607507, 0.057142857)
(0.05839416, 0.0608365)
(0.0625, 0.064386316)
(0.066390045, 0.068085104)
(0.07017544, 0.07174888)
(0.074074075, 0.075294115)
(0.07804878, 0.07901235)
(0.08184143, 0.082474224)
(0.08579089, 0.08625337)
(4.27819e9, Inf]

In [ ]: