Exact constructions
Exact intersection points of lines given by f64 endpoints, exact predicates on them, and exact information for rounding them back to f64.
rational — Exact 2D points
Exact intersection points of lines, and the exact predicates that compare them: the operations a planar arrangement needs to stay topologically consistent. The items are also re-exported at the crate root.
RationalCoord2d
pub struct RationalCoord2d { /* private fields */ }
An exact point in the plane, stored as two integer numerators over one
positive integer denominator. Every value is exact; nothing is rounded. A
RationalCoord2d is either an input f64 point, or the
intersection of two lines whose four endpoints are f64 points. There is
deliberately no way to build one from other constructed points, which is what keeps
every computation on them within fixed, proven integer widths.
The fields are private, so an invalid value cannot be built. The derived
== compares the stored representation, not the point:
the same point can be stored with different denominators. To ask whether two values
are the same point, use rational_coords_equal.
use arithmetica::{RationalCoord2d, rational_coords_equal};
// An input vertex: denominator 1, exact by construction.
let v = RationalCoord2d::from_f64([1.0, 1.0]).unwrap();
// A constructed point: where the diagonals of the square [0, 2]^2 cross.
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [2.0, 2.0], [0.0, 2.0], [2.0, 0.0])
.unwrap() // the inputs are finite
.unwrap(); // the lines are not parallel
// Same point, different representation: compare values, not fields.
assert!(rational_coords_equal(&v, &p));
assert_ne!(v, p);
RationalCoord2d::from_f64
pub fn from_f64(p: [f64; 2]) -> Result<RationalCoord2d, NonFinite>
Represents an input point exactly, with denominator 1. This is how an input vertex takes part in comparisons with constructed points.
Errors: Err(NonFinite) if either coordinate is NaN or
infinite.
use arithmetica::RationalCoord2d;
assert!(RationalCoord2d::from_f64([0.1, -2.5]).is_ok());
assert!(RationalCoord2d::from_f64([f64::INFINITY, 0.0]).is_err());
RationalCoord2d::from_segment_intersection
pub fn from_segment_intersection(a0: [f64; 2], a1: [f64; 2], b0: [f64; 2], b1: [f64; 2]) -> Result<Option<RationalCoord2d>, NonFinite>
The exact intersection point of the line through a0 and
a1 with the line through b0 and b1. Returns
Ok(None) when the lines are exactly parallel or identical, which is a
fact about the input rather than an error. This is a line intersection:
whether the point lies within either segment is a separate question, answered exactly
by orient2d on the
endpoints.
Errors: Err(NonFinite) if any of the eight coordinates
is NaN or infinite.
use arithmetica::RationalCoord2d;
// y = x/3 meets y = 1 - x/2 at (6/5, 2/5), which has no exact f64 form.
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [3.0, 1.0], [0.0, 1.0], [2.0, 0.0]);
assert!(matches!(p, Ok(Some(_))));
// Parallel lines have no unique intersection.
let q = RationalCoord2d::from_segment_intersection([0.0, 0.0], [1.0, 0.0], [0.0, 1.0], [1.0, 1.0]);
assert_eq!(q, Ok(None));
RationalCoord2d::f64_bracket
pub fn f64_bracket(&self) -> [F64Bracket; 2]
For each coordinate, the two adjacent f64 values that bracket it
and its exact position between them. No value is rounded: choosing a neighbor is a
rounding policy, and the crate leaves it to the caller (see
BracketPosition). That choice is the one
place an approximation enters, so a caller must re-check any geometric property it
needs on the rounded coordinates.
use arithmetica::{BracketPosition, RationalCoord2d};
// The point (6/5, 2/5).
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [3.0, 1.0], [0.0, 1.0], [2.0, 0.0])
.unwrap()
.unwrap();
let [x, _y] = p.f64_bracket();
assert!(x.lo < x.hi); // 6/5 is not representable
assert_eq!(x.position, BracketPosition::BelowMidpoint); // closer to lo
assert_eq!(x.lo, 1.2); // so round-to-nearest gives 1.2_f64
rational_coords_equal
pub fn rational_coords_equal(a: &RationalCoord2d, b: &RationalCoord2d) -> bool
Whether two values are exactly the same point, compared by cross-multiplication with no division and no tolerance. Points built from different pairs of lines compare equal if and only if they coincide.
use arithmetica::{RationalCoord2d, rational_coords_equal};
// Two different pairs of lines through the same point (1, 1).
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [2.0, 2.0], [0.0, 2.0], [2.0, 0.0])
.unwrap()
.unwrap();
let q = RationalCoord2d::from_segment_intersection([1.0, 0.0], [1.0, 3.0], [0.0, 1.0], [5.0, 1.0])
.unwrap()
.unwrap();
assert!(rational_coords_equal(&p, &q));
rational_compare_along
pub fn rational_compare_along(seg_start: [f64; 2], seg_end: [f64; 2], a: &RationalCoord2d, b: &RationalCoord2d) -> Result<Ordering, NonFinite>
Orders two points by their position along the direction
seg_end − seg_start: Less if a comes
first, Greater if b does, Equal if they project
to the same position. This is how the crossings on an edge are put in order.
The result is a strict weak ordering, so it is safe to use as a sort comparator: it is
computed exactly, and a tolerance-based comparator can be intransitive. Note that
Equal does not mean the same point; two points on a common perpendicular
to the direction compare Equal.
Errors: Err(NonFinite) if any coordinate of
seg_start or seg_end is NaN or infinite.
use core::cmp::Ordering;
use arithmetica::{RationalCoord2d, rational_compare_along};
// The point (6/5, 2/5), and an input vertex at x = 1/4.
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [3.0, 1.0], [0.0, 1.0], [2.0, 0.0])
.unwrap()
.unwrap();
let v = RationalCoord2d::from_f64([0.25, 5.0]).unwrap();
// Along the positive x direction, v comes first.
assert_eq!(rational_compare_along([0.0, 0.0], [1.0, 0.0], &v, &p), Ok(Ordering::Less));
assert_eq!(rational_compare_along([0.0, 0.0], [1.0, 0.0], &p, &v), Ok(Ordering::Greater));
orient2d_rational
pub fn orient2d_rational(pa: [f64; 2], pb: [f64; 2], c: &RationalCoord2d) -> Result<Sign, NonFinite>
The orientation of two input points and one constructed point, with exactly
the convention of orient2d:
Positive if c lies left of the directed line
pa → pb, Negative if right,
Zero if exactly on it.
Errors: Err(NonFinite) if any coordinate of
pa or pb is NaN or infinite.
use arithmetica::{RationalCoord2d, Sign, orient2d_rational};
// The point (6/5, 2/5), constructed on the line through (0, 0) and (3, 1).
let p = RationalCoord2d::from_segment_intersection([0.0, 0.0], [3.0, 1.0], [0.0, 1.0], [2.0, 0.0])
.unwrap()
.unwrap();
// It lies exactly on that line, although its coordinates are not f64 values.
assert_eq!(orient2d_rational([0.0, 0.0], [3.0, 1.0], &p), Ok(Sign::Zero));
// And to the left of the x-axis directed rightward.
assert_eq!(orient2d_rational([0.0, 0.0], [1.0, 0.0], &p), Ok(Sign::Positive));
Rounding brackets
The exact rounding information returned by RationalCoord2d::f64_bracket. Both types are at the crate root.
F64Bracket
pub struct F64Bracket { pub lo: f64, pub hi: f64, pub position: BracketPosition }
The two representable values that bracket an exact value, with
lo ≤ value ≤ hi, and the value’s exact position between them.
When the value is representable, lo == hi and the position is
Exact; otherwise lo and hi are adjacent
f64 values.
At the range extremes, a value beyond f64::MAX is bracketed by
f64::MAX and infinity, with the position measured against the IEEE
overflow threshold, so the IEEE tie rule reproduces IEEE overflow exactly. A value
below half the smallest subnormal is bracketed by 0.0 and
2−1074. Both cases are reachable from finite input: nearly parallel
lines can meet arbitrarily far away.
BracketPosition
pub enum BracketPosition { Exact, BelowMidpoint, AtMidpoint, AboveMidpoint }
Where an exact value sits between lo and hi:
exactly representable, strictly closer to lo, exactly halfway, or
strictly closer to hi. Every variant is an exact statement, decided by
exact comparison rather than an epsilon, so any rounding rule is a one-line selection.
Round to nearest, ties to even, the IEEE 754 default:
use arithmetica::{BracketPosition, F64Bracket};
// Round to nearest, ties to even: the policy is one match away.
fn round_nearest_even(b: F64Bracket) -> f64 {
match b.position {
BracketPosition::Exact | BracketPosition::BelowMidpoint => b.lo,
BracketPosition::AboveMidpoint => b.hi,
BracketPosition::AtMidpoint => {
if b.lo.to_bits() % 2 == 0 { b.lo } else { b.hi }
}
}
}
let exact = F64Bracket { lo: 2.0, hi: 2.0, position: BracketPosition::Exact };
assert_eq!(round_nearest_even(exact), 2.0);