Classroom Examples of Robustness Problems in Geometric Computations
Classroom Examples of Robustness Problems in Geometric Computations
Lutz Kettner† Kurt Mehlhorn† Sylvain Pion‡ Stefan Schirra§ Chee Yap¶
Abstract
The algorithms of computational geometry are designed for a machine model with exact real
arithmetic. Substituting floating-point arithmetic for the assumed real arithmetic may cause imple-
mentations to fail. Although this is well known, there is no comprehensive documentation of what
can go wrong and why. In this paper, we study simple algorithms for planar convex hulls and 3d
Delaunay triangulations and give examples that make the algorithms fail in many different ways.
For the incremental planar convex hull algorithm our examples cover the negation space of the
correctness properties of the algorithms. We also show how to construct failure-examples semi-
systematically and discuss the geometry of the floating-point implementation of the orientation
predicate. We hope that our work will be useful for teaching computational geometry.
1 Introduction
The algorithms of computational geometry are designed for a machine model with exact real arithmetic.
Substituting floating-point arithmetic for the assumed real arithmetic may cause implementations to
fail. Although this is well known, it is not common knowledge. There is no paper that systematically
discusses what can go wrong and provides simple examples for the different ways in which floating-
point implementations can fail. Due to this lack of examples,
1. instructors of computational geometry have little material for demonstrating the inadequacy of
floating-point arithmetic for geometric computations,
3. researchers in our and neighboring disciplines still believe that simple approaches are able to
overcome the problem.
∗ Partially supported by the IST Programme of the EU under Contract No IST-2000-26473, Effective Computational
Geometry for Curves and Surfaces (ECG). A preliminary version of this paper appeared at ESA 2004, LNCS 3221, pages
702 – 713.
† MPI für Informatik, Saarbrücken, {kettner,mehlhorn}@mpi-inf.mpg.de
‡ INRIA Sophia Antipolis, [email protected]
§ Otto-von-Guericke-Universität, Magdeburg, [email protected]
¶ New York University, New York, USA, [email protected]
1
p2 , p3 p2 , p3
p1
Figure 1: Results of a convex hull algorithm using double-precision floating-point arithmetic with the
coordinate axes drawn to give the reader a frame of reference. The algorithm makes gross mistakes
(from left to right): The clearly extreme point p1 is left out. The convex hull has a large concave corner
with a (non-visible) self intersection near p2 and p3 , which are close together. The convex hull has
a clearly visible concave chain (and no self-intersection). Details on these examples are explained in
Section 4.
In this paper, we study simple algorithms for two simple geometric problems, namely computing
convex hulls and triangulations of point sets, and show how they can fail and explain why they fail
when executed with floating-point arithmetic.
The convex hull CH(S) of a set S of points in the plane is the smallest convex polygon containing
S. A point p ∈ S is called extreme in S if CH(S) 6= CH(S \ p). The extreme points of S form the
vertices of the convex hull polygon. Convex hulls can be constructed incrementally. One starts with
three non-collinear points in S and then considers the remaining points in arbitrary order. When a point
is considered and lies inside the current hull, the point is simply discarded. When the point lies outside,
the tangents to the current hull are constructed and the hull is updated appropriately. We give a more
detailed description of the algorithm in Section 4.1 and the complete C++ program in Appendix A.
Figure 1 shows point sets (we give the numerical coordinates of the points in Section 4) and the
respective convex hulls computed by the floating-point implementation of our algorithm. In each case
the input points are indicated by small circles, the computed convex hull polygon is shown in green, and
the alleged extreme points are shown as filled red circles. The examples show that the implementation
may make gross mistakes. It may leave out points that are clearly extreme, it may compute polygons
that are clearly non-convex, and it may even run forever.
The first contribution of this paper is to provide a set of instances that make the floating-point
implementations fail in disastrous ways. The computed results do not resemble the correct results in
any reasonable sense.
Our second contribution is to explain why these disasters happen. The correctness of geometric
algorithms depends on geometric properties, e.g., a point lies outside a convex polygon if and only if it
can see one of the edges from the outside. We give examples, for which a floating-point implementation
violates these properties: a point outside a convex polygon that sees no edge and a point not outside
that sees some edges (both in a floating-point implementation of “sees”). We give examples for all
possible violations of the correctness properties of our convex hull algorithms.
Our third contribution is to show how difficult examples can be constructed systematically or at
least semi-systematically. This should allow others to do similar studies.
2
We believe that the paper and its companion web page will be useful in teaching computational
geometry, and that even experts will find it surprising and instructive in how many ways and how badly
even simple algorithms can be made to fail. The companion web page1 contains the source code of all
programs, the input files for all examples, and installation procedures. It allows the reader to perform
our and further experiments.
Numerical analysts are well aware of the pitfalls of floating point computation [For70]. Forsythe’s
paper and many numerical analysis textbooks, see for example [DH91, page 9], contain instructive
examples of how popular algorithms, e.g., Gaussian elimination, can fail when used with floating
point arithmetic. These examples have played a guiding role in the development of robust numeri-
cal methods. Our examples are in the same spirit, but concentrate on the geometric consequences of
approximate arithmetic. While sophisticated machinery was developed for making numerical compu-
tations reliable over the past 50 years, a corresponding machinery for geometric computation does not
yet exist to the same extent. However, significant progress was made over the past 15 years and we
point the reader to approaches to reliable geometric computing in the conclusions: the exact computa-
tion paradigm, algorithms with reduced arithmetic demand, approximate algorithms, and perturbation
methods. In our recent courses on geometric computing, we have used the warning negative examples
of this paper to raise student awareness for the problem and then discussed the approaches mentioned
in the conclusions.
This paper is structured as follows. In Section 2 we discuss the ground rules for our experiments. In
Section 3 we study the effect of floating-point arithmetic on one of the most basic predicates of planar
geometry, the orientation predicate. In Section 4 we discuss the incremental and the gift-wrapping
algorithm for planar convex hulls and in Section 5 we briefly discuss an incremental algorithm for
3d Delaunay triangulations. In Section 6 we discuss two frequently suggested simple approaches for
making the planar convex hull algorithm more robust and argue that they fail. Finally, Section 7 offers
a short conclusion and points to approaches to reliable geometric computation.
Related Work: The literature contains a small number of documented failures due to numerical im-
precision, e.g., Forrest’s seminal paper on implementing the point-in-polygon test [For85], Shewchuk’s
example for divide-and-conquer Delaunay triangulation [She97], Ramshaw’s braided lines [MN99,
Section 9.6.2], Schirra’s example for convex hulls [MN99, Section 9.6.1], and the sweep line algo-
rithm for line segment intersection and boolean operations on polygons [MN99, Sections 10.7.4 and
10.8.4].
3
Our numerical example data will be written in decimals (for human consumption). Such decimal
values, when read into the machine, are internally represented by the nearest double. We have made
sure that our data can be safely converted in this manner, e.g., conversion to binary and back to decimal
is the identity operation. However, the C++ standard library does not provide sufficient guarantees and
we offer additionally the binary data in little-endian format on the accompanying web page.
The programs were developed with the help of C GAL, the Computational Geometry Algorithms
Library,3 and L EDA, the Library of Efficient Data Types and Algorithms4 [KN04, FGK+ 00, MN99].
To simplify the use in the classroom, the convex hull algorithms presented in this paper can be used
independently of these (and other) libraries.
4
0.5 0.50000000000002531 0.5
p:
0.5 0.5000000000000171 0.5
12 17.300000000000001 8.8000000000000007
q:
12 17.300000000000001 8.8000000000000007
24 24.00000000000005 12.1
r:
24 24.0000000000000517765 12.1
(a) (b) (c)
Figure 2: The weird geometry of the float-orientation predicate: The figure shows the results of
float orient((px + xu, py + yu, q, r) for 0 ≤ x, y ≤ 255, where u = 2−53 is the increment between ad-
jacent floating-point numbers in the considered range. The result is color coded:5 Yellow (red, blue,
resp.) pixels represent collinear (negative, positive, resp.) orientation. The line through q and r is
shown in black.
Figure 2(a) shows the result of our first experiment: We use the line defined by the points q =
(12, 12) and r = (24, 24) and query it near p = (0.5, 0.5). We urge the reader to pause for a moment
and to sketch what he/she expects to see. The authors expected to see a yellow band around the
diagonal with nearly straight boundaries. Even for points with such simple coordinates the geometry
of float orient is quite weird: the set of yellow points (= the points classified as on the line) does not
resemble a straight line and the sets of red or blue points do not resemble half-spaces. We even have
points that change the side of the line, i.e., are lying left of the line and being classified as right of the
line and vice versa.
In Figures 2(b) and (c) we have given our base points more complex coordinates by adding some
digits behind the binary point. This enhances the cancellation effects in the evaluation of float orient
and leads to even more striking pictures. In (b), the red region looks like a step function at first sight.
Note however, it is not monotone, has yellow rays extending into it, and red lines extruding from
it. The yellow region (= on-region) forms blocks along the line. Strangely enough, these blocks are
separated by blue and red lines. Finally, many points change sides. In Figure (c), we have yellow blocks
of varying sizes along the diagonal, thin yellow and partly red lines extending into the blue region
(similarly for the red region), red points (the left upper corners of the yellow structures extending into
the blue region) deep inside the blue region, and isolated yellow points almost 100 units away from the
diagonal.
All diagrams in Figure 2 exhibit block structure. We now explain why: We focus on one dimension,
i.e., assume we keep y fixed and vary only x. We evaluate float orient((px + xu, py + yu), q, r) for
0 ≤ x ≤ 255, where u is the increment between adjacent floating-point numbers in the considered range.
Recall that orientation(p, q, r) = sign((qx − px )(ry − py ) − (qy − py )(rx − px )). We incur round-off
5
Figure 3: We repeat the example from Figure 2(b) and show the result for all three distinct choices for
the pivot; namely p on the left, q in the middle, and r on the right. All figures exhibit sign reversal.
errors in the additions/subtractions and also in the multiplications. Consider first one of the differences,
say qx − px . In (a), we have qx = 12 and px ≈ 0.5. Since 12 has four binary digits, we loose the
last four bits of x in the subtraction, in other words, the result of the subtraction qx − px is constant
for 24 consecutive values of x. Because of rounding to nearest, the intervals of constant value are
[8, 23], [24, 39], [40, 55] . . . . Similarly, the floating-point result of rx − px is constant for 25 consecutive
values of x. Because of rounding to nearest, the intervals of constant value are [16, 47], [48, 69], . . . .
Overlaying the two progressions gives intervals [16, 23], [24, 39], [40, 47], [48, 55], . . . and this explains
the structure we see in the rows of (a). We see short blocks of length 8, 16, 24, . . . in (a). In (b) and
(c), the situation is somewhat more complicated. It is again true that we have intervals for x, where the
results of the subtractions are constant. However, since q and r have more complex coordinates, the
relative shifts of these intervals are different and hence we see narrow and broad features.
Some Theory: We show that if all point coordinates are chosen from the range [ 12 , 1], more generally,
differ by a factor of at most two, then the only sign error is rounding to zero. According to Sterbenz’s
theorem [Ste74], floating-point subtraction of two floating-point numbers a and b is exact, if 21 ≤ ab ≤ 2
and so there will be no cancellation in the subtraction of point coordinates. Cancellation can only occur
in the evaluation of the final expression of the form cd − e f . If cd = e f then the floating-point sign
evaluation will return zero, since the double nearest to cd and e f is the same. If cd ≥ e f , the result of
computing cd in floating-point arithmetic is at least as large as the result of computing e f in floating-
point arithmetic. Thus, the floating-point evaluation of cd − e f results in a non-negative number. We
conclude that the only sign error is rounding to zero. Because of this analysis, we choose our point
coordinates from a larger range in our examples.
Choice of a Pivot Point: The orientation predicate is the sign of a three-by-three determinant and this
determinant may be evaluated in different ways. In float orient as defined above we use the point p as
the pivot, i.e., we subtract the row representing the point p from the other rows and reduce the problem
to the evaluation of a two-by-two determinant. Similarly, we may choose one of the other points as
the pivot. Figure 3 displays the effect of the different choices of the pivot point on the example of
Figure 2(b). The choice of the pivot makes a difference, but nonetheless the geometry remains non-
trivial and sign reversals happen for all three choices.
Based on floating-point error-bound estimates one can conclude that the center point w.r.t. the
6
(a) (b)
Figure 4: Examples of the impact of extended double arithmetic. We repeat the example from Fig-
ure 2(b) with different implementations of the orientation test: (a) We evaluate (qx − px )(ry − py ) and
(qy − py )(rx − px ) in extended double arithmetic, convert their values to double precision, and compare
them. (b) We evaluate sign((qx − px )(ry − py ) − (qy − py )(rx − px )) in extended double arithmetic. For
both, the grid size u = 2−53 is the same as for the regular double precision examples in Figure 2.
x-coordinate (or equivalently the y-coordinate) is the best choice for the pivot. But we have never
seen this actually realized to achieve better robustness for this predicate, probably because the planar
orientation predicate is so simple that the necessary conditional branching would already impair its
performance significantly. And, if one is willing to invest that time, one can also realize one of the
floating-point filter based exact implementation schemes, see for example [FvW96, She97]. Further
details are beyond the scope of this paper.
Extended Double Precision: Some architectures, for example, Intel Pentium processors, offer IEEE
extended double precision with a 64 bit mantissa in an 80 bit representation. Does this additional
precision help? Not really, as the examples in Figure 4 suggest. One might argue that the number of
misclassified points decreases, but the geometry of float orient remains fractured and exploitable for
failures similar to those that we develop below for the double precision arithmetic.
7
The algorithm maintains the current hull as a circular list L = (v0 , v1 , . . . , vk−1 ) of its extreme points
in counter-clockwise order. The line segments (vi , vi+1 ), 0 ≤ i ≤ k − 1 (indices are modulo k) are the
edges of the current hull. If orientation(vi , vi+1 , r) < 0, we say that r sees the edge (vi , vi+1 ) and that
the edge (vi , vi+1 ) is visible from r. If orientation(vi , vi+1 , r) ≤ 0, we say that the edge (vi , vi+1 ) is
weakly visible from r. After initialization, k ≥ 3. The following properties are key to the operation of
the algorithm.
Property A. A point r is outside CH iff r can see an edge of CH.
Property B. If r is outside CH, the edges weakly visible from r form a non-empty consecutive sub-
chain; so do the edges that are not weakly visible from r.
If (vi , vi+1 ), . . . , (v j−1 , v j ) is the subsequence of weakly visible edges, the updated hull is obtained
by replacing the subsequence (vi+1 , . . . , v j−1 ) by r. The subsequence (vi , . . . , v j ) is taken in the circular
sense, e.g., if i > j then the subsequence is (vi , . . . , vk−1 , v0 , . . . , v j ). From these properties, we derive
the following algorithm:
To turn the sketch into an algorithm, we provide more information about the substeps:
1. How does one determine whether there is an edge visible from r? We iterate over the edges in
L, checking each edge using the orientation predicate. If no visible edge is found, we discard r.
Otherwise, we take any one of the visible edges as the starting edge for the next item.
2. How does one identify the subsequence (vi , . . . , v j )? Starting from a visible edge e, we move
counter-clockwise along the boundary until a non-weakly-visible edge is encountered. Similarly,
we move clockwise from e until a non-weakly-visible edge is encountered.
3. How to update the list L? We can delete the vertices in (vi+1 , . . . , v j−1 ) after all visible edges are
found, as suggested in the above sketch (“the off-line strategy”) or we can delete them concur-
rently with the search for weakly visible edges (“the on-line strategy”).
We give a detailed implementation in Appendix A; it was used for all experiments. There are four
logical ways to negate Properties A and B:
• Failure A1 : A point outside the current hull sees no edge of the current hull.
• Failure A2 : A point inside the current hull sees an edge of the current hull.
• Failure B1 : A point outside the current hull sees all edges of the convex hull.
• Failure B2 : A point outside the current hull sees a non-contiguous set of edges.
Failures A1 and A2 are equivalent to the negation of Property A. Similarly, Failures B1 and B2 are
complete for Property B if we take A1 into account. Are all these failures realizable? We now affirm
this.
8
p7
p8
p2 , p3
p9
p6 r
p1
q
x
p5
p
p4
(a) (b)
Figure 5: (a) The convex hull illustrating Failure A1 : The point p4 in the lower left corner is left out of
the hull. (b) Schematic view indicating the ridiculous situation of a point outside the current hull and
seeing no edge of the hull: x lies to the left of all sides of the triangle (p, q, r).
Failure A1 : A point outside the current hull sees no edge of the current hull: Consider the set of
points below. Figure 5(a) shows the computed convex hull, where a point that is clearly extreme was
left out of the hull.
p1 = ( 7.3000000000000194, 7.3000000000000167 ) float orient(p1 , p2 , p3 ) > 0
p2 = (24.000000000000068, 24.000000000000071 ) float orient(p1 , p2 , p4 ) > 0
p3 = (24.00000000000005, 24.000000000000053 ) float orient(p2 , p3 , p4 ) > 0
p4 = ( 0.50000000000001621, 0.50000000000001243) float orient(p3 , p1 , p4 ) > 0 (!!)
p5 = ( 8, 4) p6 = ( 4, 9) p7 = (15, 27)
p8 = (26, 25) p9 = (19, 11)
What went wrong? The culprits are the first four points. They lie almost on the line y = x, and
float orient gives the results shown above. Only the last evaluation is wrong, indicated by “(!!)”.
Geometrically, these four evaluations say that p4 sees no edge of the triangle (p1 , p2 , p3 ). Figure 5(b)
gives a schematic view of this ridiculous situation. The points p5 , . . . , p9 are then correctly identified
as extreme points and are added to the hull. However, the algorithm never recovers from the error made
when considering p4 and the result of the computation differs drastically from the correct hull.
We next explain how we arrived at the instance above. Intuition told us that an example (if it exists
at all) would be a triangle with two almost parallel sides and with a query point near the wedge defined
9
p1 : (17.300000000000001, 17.300000000000001) (7.3000000000000194, 7.3000000000000167)
p2 : (24.000000000000068, 24.000000000000071) (24.000000000000068, 24.000000000000071)
p3 : (24.00000000000005, 24.000000000000053) (24.00000000000005, 24.000000000000053)
p4 : (0.50000000000000711, 0.5) (0.50000000000000355, 0.5)
(a) (b)
Figure 6: The points (p1 , p2 , p3 ) form a counter-clockwise triangle and we are interested in the clas-
sification of points (x(p4 ) + xu, y(p4 ) + yu) with respect to the edges (p1 , p2 ) and (p3 , p1 ) incident to
p1 . The extensions of these edges are indistinguishable in the pictures and are drawn as a single black
line. The red points do not “float-see” either one of the edges (Failure A1 ). Points collinear with one
of the edges are ocher, those collinear with both edges are yellow, those classified as seeing one but
not the other edge are blue, and those seeing both edges are green. (a) Example starting from points in
Figure 2. (b) Example that achieves “robustness” with respect to the first three points.
by the two nearly parallel edges. In view of Figure 2 such a point might be mis-classified with respect
to one of the edges and hence would be unable to see any edge of the triangle. So we started with
the points used in Figure 2(b), i.e., p1 ≈ (17, 17), p2 ≈ (24, 24) ≈ p3 , where we moved p2 slightly
to the right so as to guarantee that we obtain a counter-clockwise triangle. We then probed the edges
incident to p1 with points p4 in and near the wedge formed by these edges. Figure 6(a) visualizes
the outcomes of the two relevant orientation tests. Each red pixel is a candidate for Failure A1 . The
example obtained in this way was not completely satisfactory, since some orientation tests on the initial
triangle (p1 , p2 , p3 ) were evaluating to zero.
We perturbed the example further, aided by visualizing float orient(p1 , p2 , p3 ), until we found the
example shown in (b). The final example has the nice property that all possible float orient tests on
the first three points are correct. So this example is independent from any conceivable initialization
an algorithm could use to create the first valid triangle. Figure 6(b) shows the outcomes of the two
orientations tests for our final example.
Failure A2 : A point inside the current hull sees an edge of the current hull: Such examples are
plenty. We take any counter-clockwise triangle and choose a fourth point inside the triangle but close
to one of the edges. By Figure 2 there is the chance of sign reversal. A concrete example follows:
10
p2 p4
p3 p1
Figure 7: Schematic view of Failure B1 : The point p4 sees all edges of the triangle (p1 , p2 , p3 ).
Failure B1 : A point outside the current hull sees all edges of the convex hull: Intuition told us
that an example (if it exists) would consist of a triangle with one angle close to π and hence three
almost parallel sides. Where should one place the query point? We first placed it in the extension of
the three parallel sides and quite a distance away from the triangle. This did not work. The choice that
worked is to place the point near one of the sides so that it could see two of the sides and “float-see”
the third. Figure 7 illustrates this choice. A concrete example follows:
p1 = ( 200.0, 49.200000000000003) float orient(p1 , p2 , p3 ) > 0
p2 = ( 100.0, 49.600000000000001) float orient(p1 , p2 , p4 ) < 0
p3 = (−233.33333333333334, 50.93333333333333 ) float orient(p2 , p3 , p4 ) < 0
p4 = ( 166.66666666666669, 49.333333333333336) float orient(p3 , p1 , p4 ) < 0 (!!)
The first three points form a counter-clockwise oriented triangle and according to float orient, the
algorithm believes that p4 can see all edges of the triangle. What will our algorithm do? It depends on
the implementation details. If the algorithm first searches for an invisible edge, it will search forever
and never terminate. If it deletes points on-line from L it will crash or compute nonsense depending on
the details of the implementation.
Failure B2 : A point outside the current hull sees a non-contiguous set of edges: Consider the
following points:
p1 = ( 0.50000000000001243, 0.50000000000000189) float orient(p1 , p4 , p5 ) < 0 (!!)
p2 = ( 0.50000000000001243, 0.50000000000000333) float orient(p4 , p3 , p5 ) > 0
p3 = (24.00000000000005, 24.000000000000053 ) float orient(p3 , p2 , p5 ) < 0
p4 = (24.000000000000068, 24.000000000000071 ) float orient(p2 , p1 , p5 ) > 0
p5 = (17.300000000000001, 17.300000000000001 )
Inserting the first four points results in the convex quadrilateral (p1 , p4 , p3 , p2 ); this is correct. The
last point p5 sees only the edge (p3 , p2 ) and none of the other three. However, float orient makes p5
see also the edge (p1 , p4 ). The subsequences of visible and invisible edges are not contiguous. Since
the falsely classified edge (p1 , p4 ) comes first, our algorithm inserts p5 at this edge, removes no other
vertex, and returns a polygon that has self-intersections and is not simple.
11
(a) (b)
Figure 8: Visualization of the region of interest for the points p1 and p2 for the Failure B2 data set. (a)
Candidates can be chosen from the red regions and must be below the black line. (b) Not all candidates
will give rise to a proper convex hull for the first four points. All invalid candidates are masked out in
light grey.
We next discuss how we found the instance illustrating Failure B2 . Intuition told us that an example
(if it exists) would consist of a quadrilateral with two nearly parallel sides and the two other sides
being very short. A query point sitting above the middle of one of the long sides might be able to
“float-see” the opposite side of the quadrilateral. It would not see the two short sides. We took the
points in Figure 6(a) as a starting point, denote them q1 , q2 , . . . . We set p3 = q3 , p4 = q2 , p5 = q1 ,
and decided to look for p1 and p2 in the vicinity of q4 . So we searched for points p near q4 with
float orient(p, p4 , p5 ) < 0 and float orient(p3 , p1 , p) < 0 that are also below the exact lines defined
by (p3 , p5 ) and (p4 , p5 ) (the last condition ensures that p5 lies above the quadrilateral). Figure 8(a)
visualizes the region of interest for p.
In addition, the first four points should realize a convex hull with our algorithm. In particular,
unwanted classifications from float orient as collinear need to be avoided. We mask all forbidden
regions in the visualization and we obtain Figure 8(b), from which we were able to select our example
points. We selected two points on one of the vertical red lines and below the black line.
Finally, we visualize the region around p5 in Figure 9. The error is small for float orient in this
region, but nevertheless there are several points realizing Failure B2 , of which two are shown in the
magnified view.
Further Examples: Besides the four logical possibilities above, we can look at quantitative versions:
1. The point sees only a subset of the edge visible to it. Then too few points will be deleted from L.
2. The point sees a superset of the edges visible to it. Then too many points will be deleted from L.
3. A point sees visible and invisible edges. Then it is hard to say what happens.
12
Figure 9: Closeup of the neighborhood of the fifth point that causes Failure B2 , it is the lower left one
of the two red pixels, but the other at grid-distance (32, 32) from the first works also and there are
several more candidates not shown in this limited view.
The algorithm computes a convex polygon, but misses some of the extreme points: We have
already seen such an example in Failure A1 . We can modify this example so that the ratio of the areas
of the true hull and the computed hull becomes arbitrarily large. We do as in Failure A1 , but move the
fourth point towards infinity. The true convex hull has four extreme points. The algorithm misses q4 .
q1 = (0.10000000000000001, 0.10000000000000001) float orient(q1 , q2 , q3 ) < 0
q2 = (0.20000000000000001, 0.20000000000000004) float orient(q1 , q2 , q4 ) = 0 (!!)
q3 = (0.79999999999999993, 0.80000000000000004) float orient(q2 , q3 , q4 ) = 0 (!!)
q4 = (1.267650600228229 · 1030 , 1.2676506002282291 · 1030 ) float orient(q3 , q1 , q4 ) > 0
The algorithm computes a non-convex polygon: We have already given such an example in Failure
A2 . However, this failure is not visible to the naked eye. We next give examples where non-convexity
is visible to the naked eye. We consider the points:
p1 = (24.00000000000005, 24.000000000000053)
p2 = (24.0, 6.0 )
p3 = (54.85, 6.0 )
p4 = (54.850000000000357, 61.000000000000121)
p5 = (24.000000000000068, 24.000000000000071)
p6 = ( 6.0, 6.0 ).
13
p4 p4 p4
p02
p2 p3 p6 p2 p3 p6 p3
p1 p5 p1 p5 p1 p5
Figure 10: (a) The hull constructed after processing points p1 to p5 . Points p1 and p5 lie close to each
other and are indistinguishable in the upper figure. The magnified schematic view below shows that
we have a concave corner at p5 . The point p6 sees the edges (p1 , p2 ) and (p4 , p5 ), but does not see the
edge (p5 , p1 ). One of the former edges will be chosen by the algorithm as the chain of edges visible
from p6 . Depending on the choice, we obtain the hulls shown in (b) or (c). In (b), (p4 , p5 ) is found as
the visible edge, and in (c), (p1 , p2 ) is found. We refer the reader to the text for further explanations.
The figures show the coordinate axes to give the reader a frame of reference.
After the insertion of p1 to p4 , we have the convex hull (p1 , p2 , p3 , p4 ). This is correct. Point p5
lies inside the convex hull of the first four points; but float orient(p4 , p1 , p5 ) < 0. Thus p5 is inserted
between p4 and p1 and we obtain (p1 , p2 , p3 , p4 , p5 ). However, this error is not visible yet to the eye,
see Figure 10(a).
The point p6 sees the edges (p4 , p5 ) and (p1 , p2 ), but does not see the edge (p5 , p1 ). All of this is
correctly determined by float orient. Consider now the insertion process for point p6 . Depending on
where we start the search for a visible edge, we will either find the edge (p4 , p5 ) or the edge (p1 , p2 ).
In the former case, we insert p6 between p4 and p5 and obtain the polygon shown in (b). It is visibly
non-convex and has a self-intersection. In the latter case, we insert p6 between p1 and p2 and obtain
the polygon shown in (c). It is visibly non-convex.
Of course, in a deterministic implementation, we will see only one of the errors, namely (b). This
is because in our example, the search for a visible edge starts at edge (p2 , p3 ). In order to produce (c)
with our implementation we replace the point p2 by the point p02 = (24.0, 10.0). Then p6 sees (p02 , p3 )
and identifies (p1 , p02 , p3 ) as the chain of visible edges and hence constructs (c).
14
q
Figure 11: The extended rightturn(p, q, r) predicate returns true in the shaded region, which includes
the point q and the ray extending to the right of q.
W RAP S TEP(S, p)
q← p
for all s ∈ S do
if extended rightturn(p, q, s) then
q←s
return q
Note that the definition of the total order handles the case of duplicated points correctly, which allows us
to initialize q with p in the search. The main algorithm follows immediately. It handles all degeneracies
including duplicated points and singleton point sets correctly.
15
G IFT-W RAP C ONVEX H ULL A LGORITHM
I NPUT: A sequence S of n > 0 points
O UTPUT: Convex hull of S, CH ← (p1 , . . . , ph ), h > 0
CH ← ()
p, p1 ← lexicographically smallest point in S
repeat
Append p to CH
p ← W RAP S TEP(S, p)
until p = p1
Failure D1 : pq wraps the point set, but p and q are non-consecutive extreme points. Consider
the points:
p1 = (24.0, 12.0 )
p2 = ( 7.2, 7.2 )
p3 = ( 0.50000000000006584, 0.50000000000006561 )
p4 = ( 0.50000000000006917, 0.50000000000006573 )
p5 = (24.00000000000005, 24.0000000000000517765)
According to W RAP S TEP with float evaluation (p5 , p4 ) wraps {p1 , . . . , p5 }. However, p5 and p4
are not consecutive extreme points, since p3 lies between them. In fact, p3 is the lexicographically
smallest point. The wrapping starts at p3 , but unfortunately never reaches it again. Instead, the al-
gorithm reports the infinite sequence of points p3 , p4 , p1 , p5 , p4 , p1 , . . .. We analyze the progress more
closely: The initial sequence of points p3 , p4 , p1 , and p5 is correct. We call now W RAP S TEP(S, p5 ).
The order of points in S matters and we observe the values of point q local to the W RAP S TEP(S, p5 )
function call: Since q is initialized to p5 , we immediately replace it with p1 and then with p2 . Now,
we constructed p3 and p4 to have float orient(p5 , p2 , p3 ) > 0 (!!) while orientation(p5 , p2 , p3 ) < 0 and
float orient(p5 , p2 , p4 ) < 0 (!!) while orientation(p5 , p2 , p4 ) > 0. So, p3 will be skipped and the final
result of W RAP S TEP(S, p5 ) becomes p4 .
Algorithmically, Property D solely relies on Property C. We can violate Property D only by vio-
lating Property C. We could violate Property C without affecting Property D, i.e., process the points
in a wrong order in W RAP S TEP but still report the correct maximum. There would be no observable
failure. Instead, we chose to construct the following two closely related failures for Property C that
violate Property D as well.
Failure C1 : p is an extreme point, but p is (falsely) not a total order. Consider the points:
p1 = (24.00000000000005, 24.0000000000000517765)
p2 = ( 7.2, 7.2 )
p3 = ( 0.50000000000002798, 0.50000000000002631 )
p4 = (25.0, 1.0 )
16
They form a convex quadrilateral. In particular, the correct float orient(p1 , p2 , p3 ) < 0 will make
p2 the next extreme point after p1 has been found. We call now W RAP S TEP(S, p2 ). The order of
points in S matters and we observe the values of point q local to the W RAP S TEP(S, p2 ) function call:
Since q is initialized to p2 , we immediately replace it with p1 , ignore p2 , and test p3 next. However,
float orient(p2 , p1 , p3 ) > 0 (!!) and p3 will be discarded. W RAP S TEP(S, p2 ) will then report p4 as
result. Similar to the previous failure the algorithm skips over the lexicographic smallest point p3 and
will not terminate.
Interesting here is that we exploit an inconsistency in the float orient predicate under permutation
of its arguments. If we choose p1 as our pivot (i.e., p1 as first argument to float orient) we correctly
see p2 as extreme point. Instead, if we choose p2 as our pivot we incorrectly see p2 inside the convex
hull and can exploit this to trigger the wrong result from W RAP S TEP(S, p2 ).
Failure C2 : p is a (falsely) reported extreme point and thus p is not a total order. We replace
p3 in the example for Failure C1 with
p03 = ( 0.50000000000002665, 0.50000000000002665)
and obtain the same float orient evaluations as for Failure C1 , just that p03 is actually inside the convex
hull and not an extreme point, but will be falsely seen as extreme point when p1 is chosen as pivot in
float orient.
17
So in the sequel, we assume that u lies inside the convex hull of the previous points. We also do
not consider the bootstrapping phase of the incremental algorithm where the convex hull of the points
currently inserted does not yet have the full dimension, which requires additional predicates.
Failure E1 : The point location algorithm does not terminate. The termination proof relies on
the Delaunay property of the triangulation and the correct evaluation of the orientation predicate. We
search for a cycle among a small number of tetrahedra. Two tetrahedra are actually not enough because
of the obvious optimization that the algorithm does not test the tetrahedron again where it came from.
Three tetrahedra may suffice, where u lies close to the three supporting planes of the three common
facets to trigger numerical inaccuracies in the orientation test. This suggests to build a triangulation
with a central edge surrounded by three tetrahedra and to locate a point u that is approximately on this
edge as illustrated in Figure 12.
We provide a program that creates random examples of that nature and tests them for Failure E1 .
At first, the program generates five random points and verifies that their Delaunay triangulation has
the desired shape of three tetrahedra grouped around a central edge, and if not it tries another set of
points. Then, the program generates a point u near the central edge by computing a point on the edge
using approximate floating-point computations. At the end, the program locates u in the triangulation.
In fact, the point location does not terminate quite often due to inconsistent answers of the orientation
18
p0
p3 p2
p4
p1
Figure 12: Inserting a point u near the central edge (p0 , p1 ) of a Delaunay triangulation made of three
tetrahedra around that central edge.
predicate in the volume around the edge. We give here an example data set that sends the algorithm
into an infinite loop:
p0 = (0.092408271079090554, 0.1326565794620080400, 0.20816329990430305)
p1 = (0.183729934258721530, 0.0085360395142579648, 0.39535821959993456)
p2 = (0.382775030788625510, 0.2050904804319415600, 0.01038994374388480)
p3 = (0.256251824311654270, 0.6315717178093045400, 0.16190908040221075)
p4 = (0.191845325127811610, 0.0281530165464261020, 0.57432720440646179)
6 Non-Solutions
A number of approaches have been suggested to make floating point implementations work, either of
specific algorithms or in general. We point to promising approaches in the next section and discuss two
frequently suggested approaches that do not work in this section.
The first approach is specific to the planar convex hull problem. A frequently heard reaction to our
paper is that all our examples exploit the fact that the first few points are nearly collinear. If one starts
with a ”roundish” hull, or at least starts with a hull formed from the points of minimal and maximal x-
and y- coordinates, the problem will go away. We have two answers to this suggestion: Firstly, neither
way can cope with the situation that all input points are nearly collinear, and secondly, the example in
Figure 10 falsifies this suggestions. Observe that we have a ”roundish” hull after the insertion of the
points p1 to p4 and then the next two insertions lead the algorithm astray. The example can be modified
to start with points of minimal and maximal x- coordinates first, which we suggest as a possible course
exercise.
Epsilon-tweaking is another frequently suggested and used remedy, i.e., instead of comparing ex-
actly with zero, one compares with a small (absolute or relative) tolerance value epsilon. Epsilon-
tweaking simply activates rounding to zero. In the planar hull example, this will make it more likely
for points outside the current hull not to see any edges because of enforced collinearity and hence at
least failure A1 will still occur. In our examples of Section 3, the yellow band in our visualizations of
collinear pixels becomes wider, but its boundary remains as fractured as it is in the comparison with
zero, see Figure 13.
19
0.5 0.50000000000833222
p: p:
0.50000000000833222
0.5
12 12
q: q:
12 12
24 24
r: r:
24 24
(a) (b)
Figure 13: The effect of epsilon-tweaking: The figures show the result of repeating the experiment of
Figure 2(a), but using an absolute epsilon tolerance value of ε = 10−10 , i.e., three points are declared
collinear if float orient returns a value less than or equal to 10−10 in absolute value. The yellow region
of collinearity widens, but its boundary is as fractured as before. Figure (a) shows the boundary in
the direction of the positive y-axis, and Figure (b) shows the boundary in the direction of the positive
x-axis. The figures are color coded: Yellow (red, blue, resp.) pixels represent collinear (negative,
positive, resp.) orientation. The black lines correspond to the lines orientation(p, q, r) = ±ε.
Another objection argues that our examples are unrealistic since they contain near collinear point
triples or points very close together (actually the usual motivation for Epsilon-tweaking). Of course,
the examples have to look like this, otherwise there would not be room for rounding errors. But they
are realistic; firstly, practical experience shows it. Secondly, degeneracies, such as collinear point
triples, are on purpose in many data sets, since they reflect the design intent of a CAD construction
or in architecture. Representing such collinear point triples in double precision arithmetic and further
transformations lead to rounding errors that turn these triples into close to collinear point triples. And
thirdly, increasingly larger data sets increase the chance to have a bad triple of points just by bad luck,
and a single failure suffices to ruin the computation.
7 Conclusion
We provided instances that cause floating-point implementations of three basic geometric algorithms
to fail. Our instances make the algorithms fail in many different ways. We showed how to construct
such instances semi-systematically. We think that our paper and its companion web page will be useful
for classroom use and that it will alert students and researchers to the intricacies of implementing
geometric algorithms.
We want to reiterate that our goal was not to show that the specific algorithms discussed in this
paper can fail, but to give illustrative examples for what can go wrong and why. We could have used
other algorithms and implementations as the starting point of our work. After all, it is well-known that
20
most geometric implementations fail for some inputs. We have chosen the specific algorithms because
they are frequently taught and because they are so simple that one can actually discuss in full detail
what goes wrong. We see our contribution in presenting educational examples for the bigger problem
of why and how geometric algorithms can fail, studied on a level where all aspects of the problem
can still be discussed and understood in class. We hope that the examples will raise awareness for the
problem and willingness to study the various approaches to reliable geometric computation.
We do not want to leave our readers in despair and therefore close with some pointers to successful
approaches to reliable geometric computation. There are several approaches: (1) make sure that the
implementations of geometric predicates always returns the correct result or (2) change the algorithm
so that it can cope with the floating-point implementation of its geometric predicates and still computes
something meaningful or (3) perturb the input so that the floating-point implementation is guaranteed
to produce the correct result on the perturbed input [HS98, FKMS05].
The first approach, known as the exact geometric computation (EGC) paradigm, has been adopted
for the software libraries L EDA, C GAL and C ORE L IBRARY [KN04, FGK+ 00, MN99, KLPY99]. In
the second approach the interpretation of “meaningful” is a crucial and difficult problem. For further
references to these approaches we refer the reader to [Yap04, Sch00].
References
[And79] A.M. Andrew. Another efficient algorithm for convex hulls in two dimensions. Information
Processing Letters, 9:216–219, 1979.
[CS89] K.L. Clarkson and P.W. Shor. Applications of random sampling in computational geometry,
II. Journal of Discrete and Computational Geometry, 4:387–421, 1989.
[FGK+ 00] A. Fabri, G.-J. Giezeman, L. Kettner, S. Schirra, and S. Schönherr. On the design of CGAL
a computational geometry algorithms library. Softw. – Pract. Exp., 30(11):1167–1202,
2000.
[FKMS05] S. Funke, Ch. Klein, K. Mehlhorn, and S. Schmitt. Controlled perturbation for Delaunay
triangulations. In Proc. of 16th ACM-SIAM Symposium on Discrete Algorithms (SODA),
pages 1047–1056, Vancouver, Canada, 2005.
[For70] G. E. Forsythe. Pitfalls in computation, or why a math book is not enough. Technical Re-
port CS-TR-70-147, Stanford University, Computer Science Department, 1970. available
at historical.ncstrl.org/litesite-data/stan/CS-TR-70-147.pdf.
21
[FvW96] S. Fortune and C. van Wyk. Static analysis yields efficient exact integer arithmetic for
computational geometry. ACM Transactions on Graphics, 15:223–248, 1996. preliminary
version in ACM Conference on Computational Geometry 1993.
[Gol91] D. Goldberg. What every computer scientist should know about floating-point arithmetic.
ACM Computing Surveys, 23(1):5–48, 1991.
[Gra72] R.L. Graham. An efficient algorithm for determining the convex hulls of a finite point set.
Information Processing Letters, 1:132–133, 1972.
[HS98] D. Halperin and C.R. Shelton. A perturbation scheme for spherical arrangements with
application to molecular modeling. Comp. Geom.: Theory and Applications, 10, 1998.
[IEE87] IEEE standard for binary floating-point arithmetic. SIGPLAN Notices, 22(2):9–25, Febru-
ary 1987. Reprint of ANSI/IEEE Std 754-1985, The Institute of Electrical and Electronics
Engineers, Inc, 345 East 47th Street, New York, NY 10017, USA.
[KLPY99] V. Karamcheti, C. Li, I. Pechtchanski, and C. Yap. A Core library for robust numerical and
geometric computation. In 15th ACM Symp. Computational Geometry, pages 351–359,
1999.
[KN04] L. Kettner and S. Näher. Two computational geometry libraries: LEDA and CGAL. In
Jacob E. Goodman and Joseph O’Rourke, editors, Handbook of Discrete and Computa-
tional Geometry, chapter 65, pages 1435–1463. CRC Press LLC, Boca Raton, FL, second
edition, 2004.
[MN99] K. Mehlhorn and S. Näher. The LEDA Platform for Combinatorial and Geometric Com-
puting. Cambridge University Press, 1999. 1018 pages.
[Sch00] Stefan Schirra. Robustness and precision issues in geometric computation. In Jörg-Rüdiger
Sack and Jorge Urrutia, editors, Handbook of Computational Geometry, chapter 14, pages
597–632. Elsevier Science Publishers B.V. North-Holland, Amsterdam, 2000.
[She97] J. R. Shewchuk. Adaptive precision floating-point arithmetic and fast robust geometric
predicates. Discrete & Computational Geometry, 18:305–363, 1997.
[Yap04] C.K. Yap. Robust geometric computation. In J.E. Goodman and J. O’Rourke, editors,
Handbook of Discrete and Computational Geometry, chapter 41. CRC Press LLC, Boca
Raton, FL, 2nd edition, 2004.
22
We use our own plain conventional C++ point type. Worth mentioning are equality comparison
and lexicographic order used to find extreme points among collinear points in the startup phase.
The orientation test returns +1 if the points p, q, and r make a leftturn, it returns zero if they are
collinear, and it returns −1 if they form a rightturn. We implement the orientation test as explained
above with p as pivot point. Not shown here, but we make sure that all intermediate results are repre-
sented as 64 bit doubles and not as 80 bit extended doubles as it might happen, e.g., on Intel platforms.
For the initial three non-collinear points we scan the input sequence and maintain its convex hull of up
to two extreme points until we run out of input points or we find a third extreme point for the convex
hull. From there on we scan the remaining points in our main convex hull function as shown below.
The circular list used in our implementation is self explaining in its use. We assume a Standard
Template Library (STL) compliant interface and extend it with circulators, a concept similar to STL
iterators that allow the circular traversal in the list without any past-the-end position using the increment
and decrement operators. In addition, we assume a function that can remove a range in the list specified
by two non-identical circulator positions.
Our main convex hull function shown below has a conventional iterator-based interface like
other STL algorithms. It computes the extreme points in counterclockwise order of the 2d convex hull
of the points in the iterator range [first,last). It uses internally the circular list hull to store
the current extreme points and copies this list to the result output iterator at the end of the function.
It also returns the modified result iterator.
23
while ( orientation( *c_succ, *c_dest, p) <= 0)
c_succ = c_dest++;
// find cw tangent
Circulator c_pred = c_source--;
while ( orientation( *c_source, *c_pred, p) <= 0)
c_pred = c_source--;
// c˙source is the first point visible, c˙succ the last
if ( ++c_pred != c_succ)
hull.circular_remove( c_pred, c_succ);
hull.insert( c_succ, p);
break; // we processed all visible edges
}
} while ( c_source != hull.circulator_begin());
++first;
}
return std::copy( hull.begin(), hull.end(), result);
}
Our main gift wrapping function has a conventional iterator-based interface like other STL algo-
rithms. It computes the extreme points in counterclockwise order of the 2d convex hull of the points
in the iterator range [first,last). It writes the extreme points to the result output iterator and
returns the modified result iterator. The W RAP S TEP function is so small that we integrated it into
the main function.
24
for ( ForwardIter i = first; i != last; ++i) {
if ( extended_rightturn( p, q, *i))
q = *i;
}
p = q;
} while ( p != p0);
return result;
}
25