use super::Vector;
use super::exact_float::ExactFloat;
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PreciseVector {
pub x: ExactFloat,
pub y: ExactFloat,
pub z: ExactFloat,
}
impl PreciseVector {
pub fn new(x: f64, y: f64, z: f64) -> Self {
PreciseVector {
x: ExactFloat::from(x),
y: ExactFloat::from(y),
z: ExactFloat::from(z),
}
}
pub fn from_vector(v: Vector) -> Self {
Self::new(v.x, v.y, v.z)
}
pub fn to_vector(&self) -> Vector {
Vector::new(self.x.to_f64(), self.y.to_f64(), self.z.to_f64()).normalize()
}
pub fn equal(&self, other: &Self) -> bool {
self.x == other.x && self.y == other.y && self.z == other.z
}
pub fn is_zero(&self) -> bool {
self.x.is_zero() && self.y.is_zero() && self.z.is_zero()
}
pub fn is_unit(&self) -> bool {
self.norm2() == ExactFloat::one()
}
pub fn norm2(&self) -> ExactFloat {
self.dot(self)
}
pub fn abs(&self) -> Self {
PreciseVector {
x: self.x.abs(),
y: self.y.abs(),
z: self.z.abs(),
}
}
pub fn add(&self, other: &Self) -> Self {
PreciseVector {
x: self.x.add(&other.x),
y: self.y.add(&other.y),
z: self.z.add(&other.z),
}
}
pub fn sub(&self, other: &Self) -> Self {
PreciseVector {
x: self.x.sub(&other.x),
y: self.y.sub(&other.y),
z: self.z.sub(&other.z),
}
}
pub fn mul(&self, f: &ExactFloat) -> Self {
PreciseVector {
x: self.x.mul(f),
y: self.y.mul(f),
z: self.z.mul(f),
}
}
pub fn mul_f64(&self, f: f64) -> Self {
self.mul(&ExactFloat::from(f))
}
pub fn dot(&self, other: &Self) -> ExactFloat {
self.x
.mul(&other.x)
.add(&self.y.mul(&other.y))
.add(&self.z.mul(&other.z))
}
pub fn cross(&self, other: &Self) -> Self {
PreciseVector {
x: self.y.mul(&other.z).sub(&self.z.mul(&other.y)),
y: self.z.mul(&other.x).sub(&self.x.mul(&other.z)),
z: self.x.mul(&other.y).sub(&self.y.mul(&other.x)),
}
}
pub fn largest_component(&self) -> usize {
let t = self.abs();
if t.x > t.y {
if t.x > t.z { 0 } else { 2 }
} else if t.y > t.z {
1
} else {
2
}
}
pub fn smallest_component(&self) -> usize {
let t = self.abs();
if t.x < t.y {
if t.x < t.z { 0 } else { 2 }
} else if t.y < t.z {
1
} else {
2
}
}
}
impl Default for PreciseVector {
fn default() -> Self {
PreciseVector::new(0.0, 0.0, 0.0)
}
}
impl fmt::Display for PreciseVector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {}, {})", self.x, self.y, self.z)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn is_send_sync<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn precise_vector_is_send_sync() {
is_send_sync::<PreciseVector>();
}
fn precise_eq(a: &ExactFloat, b: &ExactFloat) -> bool {
a == b
}
#[test]
fn test_roundtrip() {
let cases = [
Vector::new(0.0, 0.0, 0.0),
Vector::new(1.0, 2.0, 3.0),
Vector::new(3.0, -4.0, 12.0),
Vector::new(1.0, 1e-16, 1e-32),
];
for v in &cases {
let got = PreciseVector::from_vector(*v).to_vector();
let want = v.normalize();
assert!(
got.approx_eq(want),
"PreciseVector::from_vector({v:?}).to_vector() = {got:?}, want {want:?}"
);
}
}
#[test]
fn test_is_unit() {
let epsilon = 1e-14;
assert!(!PreciseVector::new(0.0, 0.0, 0.0).is_unit());
assert!(PreciseVector::new(1.0, 0.0, 0.0).is_unit());
assert!(PreciseVector::new(0.0, 1.0, 0.0).is_unit());
assert!(PreciseVector::new(0.0, 0.0, 1.0).is_unit());
assert!(!PreciseVector::new(1.0 + 2.0 * epsilon, 0.0, 0.0).is_unit());
assert!(!PreciseVector::new(1.0, 1.0, 1.0).is_unit());
}
#[test]
fn test_norm2() {
let cases: Vec<(PreciseVector, f64)> = vec![
(PreciseVector::new(0.0, 0.0, 0.0), 0.0),
(PreciseVector::new(0.0, 1.0, 0.0), 1.0),
(PreciseVector::new(1.0, 1.0, 1.0), 3.0),
(PreciseVector::new(1.0, 2.0, 3.0), 14.0),
(PreciseVector::new(3.0, -4.0, 12.0), 169.0),
];
for (v, want) in &cases {
let got = v.norm2();
let want_ef = ExactFloat::from(*want);
assert!(
precise_eq(&got, &want_ef),
"{v}.norm2() = {got}, want {want}"
);
}
}
#[test]
fn test_add() {
let cases = [
(
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(1.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 2.0, 3.0),
PreciseVector::new(4.0, 5.0, 7.0),
PreciseVector::new(5.0, 7.0, 10.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
PreciseVector::new(1.0, -6.0, -6.0),
PreciseVector::new(2.0, -9.0, -1.0),
),
];
for (v1, v2, want) in &cases {
let got = v1.add(v2);
assert!(got.equal(want), "{v1} + {v2} = {got}, want {want}");
}
}
#[test]
fn test_sub() {
let cases = [
(
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(1.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 2.0, 3.0),
PreciseVector::new(4.0, 5.0, 7.0),
PreciseVector::new(-3.0, -3.0, -4.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
PreciseVector::new(1.0, -6.0, -6.0),
PreciseVector::new(0.0, 3.0, 11.0),
),
];
for (v1, v2, want) in &cases {
let got = v1.sub(v2);
assert!(got.equal(want), "{v1} - {v2} = {got}, want {want}");
}
}
#[test]
fn test_mul() {
let cases: Vec<(PreciseVector, f64, PreciseVector)> = vec![
(
PreciseVector::new(0.0, 0.0, 0.0),
3.0,
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
1.0,
PreciseVector::new(1.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
0.0,
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
3.0,
PreciseVector::new(3.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
-1.0,
PreciseVector::new(-1.0, 3.0, -5.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
2.0,
PreciseVector::new(2.0, -6.0, 10.0),
),
];
for (v, f, want) in &cases {
let ef = ExactFloat::from(*f);
let got = v.mul(&ef);
assert!(got.equal(want), "{v}.mul({f}) = {got}, want {want}");
}
}
#[test]
fn test_mul_f64() {
let cases: Vec<(PreciseVector, f64, PreciseVector)> = vec![
(
PreciseVector::new(0.0, 0.0, 0.0),
3.0,
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
1.0,
PreciseVector::new(1.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
0.0,
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
3.0,
PreciseVector::new(3.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
-1.0,
PreciseVector::new(-1.0, 3.0, -5.0),
),
(
PreciseVector::new(1.0, -3.0, 5.0),
2.0,
PreciseVector::new(2.0, -6.0, 10.0),
),
];
for (v, f, want) in &cases {
let got = v.mul_f64(*f);
assert!(got.equal(want), "{v}.mul_f64({f}) = {got}, want {want}");
}
}
#[test]
fn test_dot() {
let zero = ExactFloat::zero();
let one = ExactFloat::one();
let neg3 = ExactFloat::from(-3.0);
assert!(precise_eq(
&PreciseVector::new(1.0, 0.0, 0.0).dot(&PreciseVector::new(1.0, 0.0, 0.0)),
&one,
));
assert!(precise_eq(
&PreciseVector::new(0.0, 1.0, 0.0).dot(&PreciseVector::new(0.0, 1.0, 0.0)),
&one,
));
assert!(precise_eq(
&PreciseVector::new(0.0, 0.0, 1.0).dot(&PreciseVector::new(0.0, 0.0, 1.0)),
&one,
));
assert!(precise_eq(
&PreciseVector::new(1.0, 0.0, 0.0).dot(&PreciseVector::new(0.0, 1.0, 0.0)),
&zero,
));
assert!(precise_eq(
&PreciseVector::new(1.0, 0.0, 0.0).dot(&PreciseVector::new(0.0, 1.0, 1.0)),
&zero,
));
assert!(precise_eq(
&PreciseVector::new(1.0, 1.0, 1.0).dot(&PreciseVector::new(-1.0, -1.0, -1.0)),
&neg3,
));
let v1 = PreciseVector::new(1.0, 1.0, 1.0);
let v2 = PreciseVector::new(-1.0, -1.0, -1.0);
assert!(precise_eq(&v1.dot(&v2), &v2.dot(&v1)));
}
#[test]
fn test_cross() {
let cases = [
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 1.0, 0.0),
PreciseVector::new(0.0, 0.0, 1.0),
),
(
PreciseVector::new(0.0, 1.0, 0.0),
PreciseVector::new(0.0, 0.0, 1.0),
PreciseVector::new(1.0, 0.0, 0.0),
),
(
PreciseVector::new(0.0, 0.0, 1.0),
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 1.0, 0.0),
),
(
PreciseVector::new(0.0, 1.0, 0.0),
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, -1.0),
),
(
PreciseVector::new(1.0, 2.0, 3.0),
PreciseVector::new(-4.0, 5.0, -6.0),
PreciseVector::new(-27.0, -6.0, 13.0),
),
];
for (v1, v2, want) in &cases {
let got = v1.cross(v2);
assert!(got.equal(want), "{v1} x {v2} = {got}, want {want}");
}
}
#[test]
fn test_identities() {
let zero = ExactFloat::zero();
let pairs = [
(
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 0.0, 0.0),
),
(
PreciseVector::new(0.0, 0.0, 0.0),
PreciseVector::new(0.0, 1.0, 2.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 1.0, 0.0),
),
(
PreciseVector::new(1.0, 0.0, 0.0),
PreciseVector::new(0.0, 1.0, 1.0),
),
(
PreciseVector::new(1.0, 1.0, 1.0),
PreciseVector::new(-1.0, -1.0, -1.0),
),
(
PreciseVector::new(1.0, 2.0, 2.0),
PreciseVector::new(-0.3, 0.4, -1.2),
),
];
for (v1, v2) in &pairs {
let c1 = v1.cross(v2);
let c2 = v2.cross(v1);
let d1 = v1.dot(v2);
let d2 = v2.dot(v1);
assert!(precise_eq(&d1, &d2), "dot not commutative for {v1}, {v2}");
assert!(
c1.equal(&c2.mul_f64(-1.0)),
"cross not anti-commutative for {v1}, {v2}: {c1} vs {c2}"
);
assert!(precise_eq(&v1.dot(&c1), &zero), "{v1} . ({v1} x {v2}) != 0");
assert!(precise_eq(&v2.dot(&c1), &zero), "{v2} . ({v1} x {v2}) != 0");
}
}
#[test]
fn test_largest_smallest_component() {
let cases: Vec<(PreciseVector, usize, usize)> = vec![
(PreciseVector::new(0.0, 0.0, 0.0), 2, 2),
(PreciseVector::new(1.0, 0.0, 0.0), 0, 2),
(PreciseVector::new(1.0, -1.0, 0.0), 1, 2),
(PreciseVector::new(-1.0, -1.1, -1.1), 2, 0),
(PreciseVector::new(0.5, -0.4, -0.5), 2, 1),
(PreciseVector::new(1e-15, 1e-14, 1e-13), 2, 0),
];
for (v, largest, smallest) in &cases {
assert_eq!(
v.largest_component(),
*largest,
"{v}.largest_component() = {}, want {largest}",
v.largest_component()
);
assert_eq!(
v.smallest_component(),
*smallest,
"{v}.smallest_component() = {}, want {smallest}",
v.smallest_component()
);
}
}
#[test]
fn test_is_zero() {
assert!(PreciseVector::new(0.0, 0.0, 0.0).is_zero());
assert!(PreciseVector::new(0.0, -0.0, 0.0).is_zero());
assert!(!PreciseVector::new(0.0, 0.0, 1.0).is_zero());
let x = PreciseVector::new(1e20, 0.0, 0.0);
let y = PreciseVector::new(1.0, 0.0, 0.0);
let result = x.add(&y).add(&x.mul_f64(-1.0));
assert!(!result.is_zero());
let xy = x.add(&y);
assert!(xy.sub(&xy).is_zero());
}
}
#[cfg(test)]
mod quickcheck_tests {
use super::*;
use quickcheck_macros::quickcheck;
fn pvec(x: f64, y: f64, z: f64) -> PreciseVector {
fn clamp(v: f64) -> f64 {
if v.is_finite() {
v.clamp(-1e10, 1e10)
} else {
0.0
}
}
PreciseVector::new(clamp(x), clamp(y), clamp(z))
}
#[quickcheck]
fn prop_dot_commutative(ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64) -> bool {
let a = pvec(ax, ay, az);
let b = pvec(bx, by, bz);
a.dot(&b) == b.dot(&a)
}
#[quickcheck]
fn prop_cross_orthogonal(ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64) -> bool {
let a = pvec(ax, ay, az);
let b = pvec(bx, by, bz);
let c = a.cross(&b);
a.dot(&c).is_zero() && b.dot(&c).is_zero()
}
#[quickcheck]
fn prop_cross_anticommutative(ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64) -> bool {
let a = pvec(ax, ay, az);
let b = pvec(bx, by, bz);
let c1 = a.cross(&b);
let c2 = b.cross(&a);
c1.equal(&c2.mul_f64(-1.0))
}
#[quickcheck]
fn prop_add_commutative(ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64) -> bool {
let a = pvec(ax, ay, az);
let b = pvec(bx, by, bz);
a.add(&b).equal(&b.add(&a))
}
#[quickcheck]
fn prop_sub_is_add_neg(ax: f64, ay: f64, az: f64, bx: f64, by: f64, bz: f64) -> bool {
let a = pvec(ax, ay, az);
let b = pvec(bx, by, bz);
a.sub(&b).equal(&a.add(&b.mul_f64(-1.0)))
}
#[quickcheck]
fn prop_norm2_non_negative(x: f64, y: f64, z: f64) -> bool {
let v = pvec(x, y, z);
v.norm2().signum() >= 0
}
#[quickcheck]
fn prop_self_sub_self_is_zero(x: f64, y: f64, z: f64) -> bool {
let v = pvec(x, y, z);
v.sub(&v).is_zero()
}
#[cfg(feature = "serde")]
#[quickcheck]
fn prop_serde_roundtrip(x: i32, y: i32, z: i32) -> bool {
let v = PreciseVector::new(f64::from(x), f64::from(y), f64::from(z));
let json = serde_json::to_string(&v).unwrap();
let back: PreciseVector = serde_json::from_str(&json).unwrap();
back == v
}
}