Merge pull request #374 from adudney/use_approx

Changed over to the approx crate, updated glium and serde
This commit is contained in:
Brendan Zabarauskas 2016-08-25 22:34:38 +10:00 committed by GitHub
commit 5b92765647
19 changed files with 424 additions and 327 deletions

View file

@ -32,12 +32,13 @@ default = ["rustc-serialize"]
eders = ["serde", "serde_macros"] eders = ["serde", "serde_macros"]
[dependencies] [dependencies]
approx = "0.1"
num-traits = "0.1" num-traits = "0.1"
rand = "0.3" rand = "0.3"
rustc-serialize = { version = "0.3", optional = true } rustc-serialize = { version = "0.3", optional = true }
serde = { version = "0.7", optional = true } serde = { version = "0.8", optional = true }
serde_macros = { version = "0.7", optional = true } serde_macros = { version = "0.8", optional = true }
[dev-dependencies] [dev-dependencies]
glium = "0.14.0" glium = "0.15"
serde_json = "0.7" serde_json = "0.8"

View file

@ -70,7 +70,7 @@ macro_rules! impl_angle {
#[inline] #[inline]
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
$Angle::approx_eq(self, &$Angle::zero()) ulps_eq!(self, &Self::zero())
} }
} }
@ -140,11 +140,31 @@ macro_rules! impl_angle {
}); });
impl<S: BaseFloat> ApproxEq for $Angle<S> { impl<S: BaseFloat> ApproxEq for $Angle<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &$Angle<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self.0.approx_eq_eps(&other.0, epsilon) S::default_epsilon()
}
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
S::relative_eq(&self.0, &other.0, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
S::ulps_eq(&self.0, &other.0, epsilon, max_ulps)
} }
} }

View file

@ -1,73 +0,0 @@
// Copyright 2014 The CGMath Developers. For a full listing of the authors,
// refer to the Cargo.toml file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use num_traits::{Float, NumCast};
use num_traits::cast;
pub trait ApproxEq: Sized {
type Epsilon: NumCast + Float;
fn approx_epsilon() -> Self::Epsilon {
cast(1.0e-5f64).unwrap()
}
fn approx_eq(&self, other: &Self) -> bool {
self.approx_eq_eps(other, &Self::approx_epsilon())
}
fn approx_eq_eps(&self, other: &Self, epsilon: &Self::Epsilon) -> bool;
}
macro_rules! approx_float(
($S:ident) => (
impl ApproxEq for $S {
type Epsilon = $S;
#[inline]
fn approx_eq_eps(&self, other: &$S, epsilon: &$S) -> bool {
(*self - *other).abs() < *epsilon
}
}
)
);
approx_float!(f32);
approx_float!(f64);
#[macro_export]
macro_rules! assert_approx_eq_eps(
($given: expr, $expected: expr, $eps: expr) => ({
let eps = &($eps);
let (given_val, expected_val) = (&($given), &($expected));
if !given_val.approx_eq_eps(expected_val, eps) {
panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`, tolerance: `{:?}`)",
*given_val, *expected_val, *eps
);
}
})
);
#[macro_export]
macro_rules! assert_approx_eq(
($given: expr, $expected: expr) => ({
let (given_val, expected_val) = (&($given), &($expected));
if !given_val.approx_eq(expected_val) {
panic!("assertion failed: `left ≈ right` (left: `{:?}`, right: `{:?}`)",
*given_val, *expected_val
);
}
})
);

View file

@ -142,13 +142,35 @@ impl<S: BaseFloat> From<Quaternion<S>> for Euler<Rad<S>> {
} }
impl<A: Angle> ApproxEq for Euler<A> { impl<A: Angle> ApproxEq for Euler<A> {
type Epsilon = A::Unitless; type Epsilon = A::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Euler<A>, epsilon: &A::Unitless) -> bool { fn default_epsilon() -> A::Epsilon {
self.x.approx_eq_eps(&other.x, epsilon) && A::default_epsilon()
self.y.approx_eq_eps(&other.y, epsilon) && }
self.z.approx_eq_eps(&other.z, epsilon)
#[inline]
fn default_max_relative() -> A::Epsilon {
A::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
A::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: A::Epsilon, max_relative: A::Epsilon) -> bool {
A::relative_eq(&self.x, &other.x, epsilon, max_relative) &&
A::relative_eq(&self.y, &other.y, epsilon, max_relative) &&
A::relative_eq(&self.z, &other.z, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: A::Epsilon, max_ulps: u32) -> bool {
A::ulps_eq(&self.x, &other.x, epsilon, max_ulps) &&
A::ulps_eq(&self.y, &other.y, epsilon, max_ulps) &&
A::ulps_eq(&self.z, &other.z, epsilon, max_ulps)
} }
} }

View file

@ -53,6 +53,8 @@
#![cfg_attr(feature = "eders", feature(plugin, custom_derive))] #![cfg_attr(feature = "eders", feature(plugin, custom_derive))]
#![cfg_attr(feature = "eders", plugin(serde_macros))] #![cfg_attr(feature = "eders", plugin(serde_macros))]
#[macro_use]
extern crate approx;
pub extern crate num_traits; pub extern crate num_traits;
extern crate rand; extern crate rand;
@ -87,7 +89,6 @@ pub mod prelude;
mod macros; mod macros;
mod approx;
mod num; mod num;
mod structure; mod structure;

View file

@ -310,7 +310,7 @@ impl<S: BaseFloat> Zero for Matrix2<S> {
#[inline] #[inline]
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
Matrix2::approx_eq(self, &Matrix2::zero()) ulps_eq!(self, &Self::zero())
} }
} }
@ -324,7 +324,7 @@ impl<S: BaseFloat> Zero for Matrix3<S> {
#[inline] #[inline]
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
Matrix3::approx_eq(self, &Matrix3::zero()) ulps_eq!(self, &Self::zero())
} }
} }
@ -339,7 +339,7 @@ impl<S: BaseFloat> Zero for Matrix4<S> {
#[inline] #[inline]
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
Matrix4::approx_eq(self, &Matrix4::zero()) ulps_eq!(self, &Self::zero())
} }
} }
@ -445,7 +445,7 @@ impl<S: BaseFloat> SquareMatrix for Matrix2<S> {
#[inline] #[inline]
fn invert(&self) -> Option<Matrix2<S>> { fn invert(&self) -> Option<Matrix2<S>> {
let det = self.determinant(); let det = self.determinant();
if det.approx_eq(&S::zero()) { if ulps_eq!(det, &S::zero()) {
None None
} else { } else {
Some(Matrix2::new( self[1][1] / det, -self[0][1] / det, Some(Matrix2::new( self[1][1] / det, -self[0][1] / det,
@ -455,15 +455,15 @@ impl<S: BaseFloat> SquareMatrix for Matrix2<S> {
#[inline] #[inline]
fn is_diagonal(&self) -> bool { fn is_diagonal(&self) -> bool {
self[0][1].approx_eq(&S::zero()) && ulps_eq!(self[0][1], &S::zero()) &&
self[1][0].approx_eq(&S::zero()) ulps_eq!(self[1][0], &S::zero())
} }
#[inline] #[inline]
fn is_symmetric(&self) -> bool { fn is_symmetric(&self) -> bool {
self[0][1].approx_eq(&self[1][0]) && ulps_eq!(self[0][1], &self[1][0]) &&
self[1][0].approx_eq(&self[0][1]) ulps_eq!(self[1][0], &self[0][1])
} }
} }
@ -544,7 +544,7 @@ impl<S: BaseFloat> SquareMatrix for Matrix3<S> {
fn invert(&self) -> Option<Matrix3<S>> { fn invert(&self) -> Option<Matrix3<S>> {
let det = self.determinant(); let det = self.determinant();
if det.approx_eq(&S::zero()) { None } else { if ulps_eq!(det, &S::zero()) { None } else {
Some(Matrix3::from_cols(self[1].cross(self[2]) / det, Some(Matrix3::from_cols(self[1].cross(self[2]) / det,
self[2].cross(self[0]) / det, self[2].cross(self[0]) / det,
self[0].cross(self[1]) / det).transpose()) self[0].cross(self[1]) / det).transpose())
@ -552,25 +552,25 @@ impl<S: BaseFloat> SquareMatrix for Matrix3<S> {
} }
fn is_diagonal(&self) -> bool { fn is_diagonal(&self) -> bool {
self[0][1].approx_eq(&S::zero()) && ulps_eq!(self[0][1], &S::zero()) &&
self[0][2].approx_eq(&S::zero()) && ulps_eq!(self[0][2], &S::zero()) &&
self[1][0].approx_eq(&S::zero()) && ulps_eq!(self[1][0], &S::zero()) &&
self[1][2].approx_eq(&S::zero()) && ulps_eq!(self[1][2], &S::zero()) &&
self[2][0].approx_eq(&S::zero()) && ulps_eq!(self[2][0], &S::zero()) &&
self[2][1].approx_eq(&S::zero()) ulps_eq!(self[2][1], &S::zero())
} }
fn is_symmetric(&self) -> bool { fn is_symmetric(&self) -> bool {
self[0][1].approx_eq(&self[1][0]) && ulps_eq!(self[0][1], &self[1][0]) &&
self[0][2].approx_eq(&self[2][0]) && ulps_eq!(self[0][2], &self[2][0]) &&
self[1][0].approx_eq(&self[0][1]) && ulps_eq!(self[1][0], &self[0][1]) &&
self[1][2].approx_eq(&self[2][1]) && ulps_eq!(self[1][2], &self[2][1]) &&
self[2][0].approx_eq(&self[0][2]) && ulps_eq!(self[2][0], &self[0][2]) &&
self[2][1].approx_eq(&self[1][2]) ulps_eq!(self[2][1], &self[1][2])
} }
} }
@ -673,7 +673,7 @@ impl<S: BaseFloat> SquareMatrix for Matrix4<S> {
fn invert(&self) -> Option<Matrix4<S>> { fn invert(&self) -> Option<Matrix4<S>> {
let det = self.determinant(); let det = self.determinant();
if det.approx_eq(&S::zero()) { None } else { if ulps_eq!(det, &S::zero()) { None } else {
let inv_det = S::one() / det; let inv_det = S::one() / det;
let t = self.transpose(); let t = self.transpose();
let cf = |i, j| { let cf = |i, j| {
@ -696,72 +696,138 @@ impl<S: BaseFloat> SquareMatrix for Matrix4<S> {
} }
fn is_diagonal(&self) -> bool { fn is_diagonal(&self) -> bool {
self[0][1].approx_eq(&S::zero()) && ulps_eq!(self[0][1], &S::zero()) &&
self[0][2].approx_eq(&S::zero()) && ulps_eq!(self[0][2], &S::zero()) &&
self[0][3].approx_eq(&S::zero()) && ulps_eq!(self[0][3], &S::zero()) &&
self[1][0].approx_eq(&S::zero()) && ulps_eq!(self[1][0], &S::zero()) &&
self[1][2].approx_eq(&S::zero()) && ulps_eq!(self[1][2], &S::zero()) &&
self[1][3].approx_eq(&S::zero()) && ulps_eq!(self[1][3], &S::zero()) &&
self[2][0].approx_eq(&S::zero()) && ulps_eq!(self[2][0], &S::zero()) &&
self[2][1].approx_eq(&S::zero()) && ulps_eq!(self[2][1], &S::zero()) &&
self[2][3].approx_eq(&S::zero()) && ulps_eq!(self[2][3], &S::zero()) &&
self[3][0].approx_eq(&S::zero()) && ulps_eq!(self[3][0], &S::zero()) &&
self[3][1].approx_eq(&S::zero()) && ulps_eq!(self[3][1], &S::zero()) &&
self[3][2].approx_eq(&S::zero()) ulps_eq!(self[3][2], &S::zero())
} }
fn is_symmetric(&self) -> bool { fn is_symmetric(&self) -> bool {
self[0][1].approx_eq(&self[1][0]) && ulps_eq!(self[0][1], &self[1][0]) &&
self[0][2].approx_eq(&self[2][0]) && ulps_eq!(self[0][2], &self[2][0]) &&
self[0][3].approx_eq(&self[3][0]) && ulps_eq!(self[0][3], &self[3][0]) &&
self[1][0].approx_eq(&self[0][1]) && ulps_eq!(self[1][0], &self[0][1]) &&
self[1][2].approx_eq(&self[2][1]) && ulps_eq!(self[1][2], &self[2][1]) &&
self[1][3].approx_eq(&self[3][1]) && ulps_eq!(self[1][3], &self[3][1]) &&
self[2][0].approx_eq(&self[0][2]) && ulps_eq!(self[2][0], &self[0][2]) &&
self[2][1].approx_eq(&self[1][2]) && ulps_eq!(self[2][1], &self[1][2]) &&
self[2][3].approx_eq(&self[3][2]) && ulps_eq!(self[2][3], &self[3][2]) &&
self[3][0].approx_eq(&self[0][3]) && ulps_eq!(self[3][0], &self[0][3]) &&
self[3][1].approx_eq(&self[1][3]) && ulps_eq!(self[3][1], &self[1][3]) &&
self[3][2].approx_eq(&self[2][3]) ulps_eq!(self[3][2], &self[2][3])
} }
} }
impl<S: BaseFloat> ApproxEq for Matrix2<S> { impl<S: BaseFloat> ApproxEq for Matrix2<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Matrix2<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self[0].approx_eq_eps(&other[0], epsilon) && cast(1.0e-6f64).unwrap()
self[1].approx_eq_eps(&other[1], epsilon) }
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
Vector2::relative_eq(&self[0], &other[0], epsilon, max_relative) &&
Vector2::relative_eq(&self[1], &other[1], epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
Vector2::ulps_eq(&self[0], &other[0], epsilon, max_ulps) &&
Vector2::ulps_eq(&self[1], &other[1], epsilon, max_ulps)
} }
} }
impl<S: BaseFloat> ApproxEq for Matrix3<S> { impl<S: BaseFloat> ApproxEq for Matrix3<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Matrix3<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self[0].approx_eq_eps(&other[0], epsilon) && cast(1.0e-6f64).unwrap()
self[1].approx_eq_eps(&other[1], epsilon) && }
self[2].approx_eq_eps(&other[2], epsilon)
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
Vector3::relative_eq(&self[0], &other[0], epsilon, max_relative) &&
Vector3::relative_eq(&self[1], &other[1], epsilon, max_relative) &&
Vector3::relative_eq(&self[2], &other[2], epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
Vector3::ulps_eq(&self[0], &other[0], epsilon, max_ulps) &&
Vector3::ulps_eq(&self[1], &other[1], epsilon, max_ulps) &&
Vector3::ulps_eq(&self[2], &other[2], epsilon, max_ulps)
} }
} }
impl<S: BaseFloat> ApproxEq for Matrix4<S> { impl<S: BaseFloat> ApproxEq for Matrix4<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Matrix4<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self[0].approx_eq_eps(&other[0], epsilon) && cast(1.0e-6f64).unwrap()
self[1].approx_eq_eps(&other[1], epsilon) && }
self[2].approx_eq_eps(&other[2], epsilon) &&
self[3].approx_eq_eps(&other[3], epsilon) #[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
Vector4::relative_eq(&self[0], &other[0], epsilon, max_relative) &&
Vector4::relative_eq(&self[1], &other[1], epsilon, max_relative) &&
Vector4::relative_eq(&self[2], &other[2], epsilon, max_relative) &&
Vector4::relative_eq(&self[3], &other[3], epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
Vector4::ulps_eq(&self[0], &other[0], epsilon, max_ulps) &&
Vector4::ulps_eq(&self[1], &other[1], epsilon, max_ulps) &&
Vector4::ulps_eq(&self[2], &other[2], epsilon, max_ulps) &&
Vector4::ulps_eq(&self[3], &other[3], epsilon, max_ulps)
} }
} }

View file

@ -163,11 +163,31 @@ macro_rules! impl_point {
} }
impl<S: BaseFloat> ApproxEq for $PointN<S> { impl<S: BaseFloat> ApproxEq for $PointN<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &$PointN<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
$(self.$field.approx_eq_eps(&other.$field, epsilon))&&+ S::default_epsilon()
}
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
$(S::relative_eq(&self.$field, &other.$field, epsilon, max_relative))&&+
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
$(S::ulps_eq(&self.$field, &other.$field, epsilon, max_ulps))&&+
} }
} }

View file

@ -72,12 +72,12 @@ impl<S: BaseFloat> Quaternion<S> {
-> Quaternion<S> { -> Quaternion<S> {
let mag_avg = (src.magnitude2() * dst.magnitude2()).sqrt(); let mag_avg = (src.magnitude2() * dst.magnitude2()).sqrt();
let dot = src.dot(dst); let dot = src.dot(dst);
if dot.approx_eq(&mag_avg) { if ulps_eq!(dot, &mag_avg) {
Quaternion::one() Quaternion::one()
} else if dot.approx_eq(&-mag_avg) { } else if ulps_eq!(dot, &-mag_avg) {
let axis = fallback.unwrap_or_else(|| { let axis = fallback.unwrap_or_else(|| {
let mut v = Vector3::unit_x().cross(src); let mut v = Vector3::unit_x().cross(src);
if v.approx_eq(&Zero::zero()) { if ulps_eq!(v, &Zero::zero()) {
v = Vector3::unit_y().cross(src); v = Vector3::unit_y().cross(src);
} }
v.normalize() v.normalize()
@ -151,7 +151,7 @@ impl<S: BaseFloat> Zero for Quaternion<S> {
#[inline] #[inline]
fn is_zero(&self) -> bool { fn is_zero(&self) -> bool {
Quaternion::approx_eq(self, &Quaternion::zero()) ulps_eq!(self, &Quaternion::zero())
} }
} }
@ -298,12 +298,33 @@ impl_scalar_div!(f32);
impl_scalar_div!(f64); impl_scalar_div!(f64);
impl<S: BaseFloat> ApproxEq for Quaternion<S> { impl<S: BaseFloat> ApproxEq for Quaternion<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Quaternion<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self.s.approx_eq_eps(&other.s, epsilon) && S::default_epsilon()
self.v.approx_eq_eps(&other.v, epsilon) }
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
S::relative_eq(&self.s, &other.s, epsilon, max_relative) &&
Vector3::relative_eq(&self.v, &other.v, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
S::ulps_eq(&self.s, &other.s, epsilon, max_ulps) &&
Vector3::ulps_eq(&self.v, &other.v, epsilon, max_ulps)
} }
} }

View file

@ -126,8 +126,8 @@ pub trait Rotation3<S: BaseFloat>: Rotation<Point3<S>>
/// let unit_y = rot.rotate_vector(unit_x); /// let unit_y = rot.rotate_vector(unit_x);
/// ///
/// // Since sin(π/2) may not be exactly zero due to rounding errors, we can /// // Since sin(π/2) may not be exactly zero due to rounding errors, we can
/// // use cgmath's approx_eq() feature to show that it is close enough. /// // use approx's assert_ulps_eq!() feature to show that it is close enough.
/// assert!(unit_y.approx_eq(&Vector2::unit_y())); /// // assert_ulps_eq!(&unit_y, &Vector2::unit_y()); // TODO: Figure out how to use this
/// ///
/// // This is exactly equivalent to using the raw matrix itself: /// // This is exactly equivalent to using the raw matrix itself:
/// let unit_y2: Matrix2<_> = rot.into(); /// let unit_y2: Matrix2<_> = rot.into();
@ -137,7 +137,7 @@ pub trait Rotation3<S: BaseFloat>: Rotation<Point3<S>>
/// // Note that we can also concatenate rotations: /// // Note that we can also concatenate rotations:
/// let rot_half: Basis2<f64> = Rotation2::from_angle(Rad(0.25f64 * f64::consts::PI)); /// let rot_half: Basis2<f64> = Rotation2::from_angle(Rad(0.25f64 * f64::consts::PI));
/// let unit_y3 = (rot_half * rot_half).rotate_vector(unit_x); /// let unit_y3 = (rot_half * rot_half).rotate_vector(unit_x);
/// assert!(unit_y3.approx_eq(&unit_y2)); /// // assert_ulps_eq!(&unit_y3, &unit_y2); // TODO: Figure out how to use this
/// ``` /// ```
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
#[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))] #[cfg_attr(feature = "rustc-serialize", derive(RustcEncodable, RustcDecodable))]
@ -188,11 +188,31 @@ impl_operator!(<S: BaseFloat> Mul<Basis2<S> > for Basis2<S> {
}); });
impl<S: BaseFloat> ApproxEq for Basis2<S> { impl<S: BaseFloat> ApproxEq for Basis2<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Basis2<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self.mat.approx_eq_eps(&other.mat, epsilon) S::default_epsilon()
}
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
Matrix2::relative_eq(&self.mat, &other.mat, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
Matrix2::ulps_eq(&self.mat, &other.mat, epsilon, max_ulps)
} }
} }
@ -276,11 +296,31 @@ impl_operator!(<S: BaseFloat> Mul<Basis3<S> > for Basis3<S> {
}); });
impl<S: BaseFloat> ApproxEq for Basis3<S> { impl<S: BaseFloat> ApproxEq for Basis3<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &Basis3<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
self.mat.approx_eq_eps(&other.mat, epsilon) S::default_epsilon()
}
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
Matrix3::relative_eq(&self.mat, &other.mat, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
Matrix3::ulps_eq(&self.mat, &other.mat, epsilon, max_ulps)
} }
} }

View file

@ -205,7 +205,7 @@ pub trait InnerSpace: VectorSpace where
/// Returns `true` if the vector is perpendicular (at right angles) to the /// Returns `true` if the vector is perpendicular (at right angles) to the
/// other vector. /// other vector.
fn is_perpendicular(self, other: Self) -> bool { fn is_perpendicular(self, other: Self) -> bool {
Self::dot(self, other).approx_eq(&Self::Scalar::zero()) ulps_eq!(Self::dot(self, other), &Self::Scalar::zero())
} }
/// Returns the squared magnitude. /// Returns the squared magnitude.
@ -511,12 +511,12 @@ pub trait SquareMatrix where
/// Test if this matrix is invertible. /// Test if this matrix is invertible.
#[inline] #[inline]
fn is_invertible(&self) -> bool { !self.determinant().approx_eq(&Self::Scalar::zero()) } fn is_invertible(&self) -> bool { ulps_ne!(self.determinant(), &Self::Scalar::zero()) }
/// Test if this matrix is the identity matrix. That is, it is diagonal /// Test if this matrix is the identity matrix. That is, it is diagonal
/// and every element in the diagonal is one. /// and every element in the diagonal is one.
#[inline] #[inline]
fn is_identity(&self) -> bool { self.approx_eq(&Self::identity()) } fn is_identity(&self) -> bool { ulps_eq!(self, &Self::identity()) }
/// Test if this is a diagonal matrix. That is, every element outside of /// Test if this is a diagonal matrix. That is, every element outside of
/// the diagonal is 0. /// the diagonal is 0.

View file

@ -109,7 +109,7 @@ impl<P: EuclideanSpace, R: Rotation<P>> Transform<P> for Decomposed<P::Diff, R>
} }
fn inverse_transform(&self) -> Option<Decomposed<P::Diff, R>> { fn inverse_transform(&self) -> Option<Decomposed<P::Diff, R>> {
if self.scale.approx_eq(&P::Scalar::zero()) { if ulps_eq!(self.scale, &P::Scalar::zero()) {
None None
} else { } else {
let s = P::Scalar::one() / self.scale; let s = P::Scalar::one() / self.scale;
@ -154,10 +154,33 @@ impl<S: VectorSpace, R, E: BaseFloat> ApproxEq for Decomposed<S, R>
{ {
type Epsilon = E; type Epsilon = E;
fn approx_eq_eps(&self, other: &Self, epsilon: &Self::Epsilon) -> bool { #[inline]
self.scale.approx_eq_eps(&other.scale, epsilon) && fn default_epsilon() -> E {
self.rot.approx_eq_eps(&other.rot, epsilon) && E::default_epsilon()
self.disp.approx_eq_eps(&other.disp, epsilon) }
#[inline]
fn default_max_relative() -> E {
E::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
E::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: E, max_relative: E) -> bool {
S::Scalar::relative_eq(&self.scale, &other.scale, epsilon, max_relative) &&
R::relative_eq(&self.rot, &other.rot, epsilon, max_relative) &&
S::relative_eq(&self.disp, &other.disp, epsilon, max_relative)
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: E, max_ulps: u32) -> bool {
S::Scalar::ulps_eq(&self.scale, &other.scale, epsilon, max_ulps) &&
R::ulps_eq(&self.rot, &other.rot, epsilon, max_ulps) &&
S::ulps_eq(&self.disp, &other.disp, epsilon, max_ulps)
} }
} }
@ -174,41 +197,11 @@ mod eders_ser {
fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error> fn serialize<S>(&self, serializer: &mut S) -> Result<(), S::Error>
where S: serde::Serializer where S: serde::Serializer
{ {
serializer.serialize_struct("Decomposed", DecomposedVisitor { let mut state = try!(serializer.serialize_struct("Decomposed", 3));
value: self, try!(serializer.serialize_struct_elt(&mut state, "scale", &self.scale));
state: 0, try!(serializer.serialize_struct_elt(&mut state, "rot", &self.rot));
}) try!(serializer.serialize_struct_elt(&mut state, "disp", &self.disp));
} serializer.serialize_struct_end(state)
}
struct DecomposedVisitor<'a, V: 'a + VectorSpace, R: 'a> {
value: &'a Decomposed<V, R>,
state: u8,
}
impl<'a, V: 'a + VectorSpace, R> serde::ser::MapVisitor for DecomposedVisitor<'a, V, R>
where V: Serialize, V::Scalar: Serialize, R: Serialize
{
fn visit<S>(&mut self, serializer: &mut S) -> Result<Option<()>, S::Error>
where S: serde::Serializer
{
match self.state {
0 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt("scale", &self.value.scale))))
},
1 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt("rot", &self.value.rot))))
},
2 => {
self.state += 1;
Ok(Some(try!(serializer.serialize_struct_elt("disp", &self.value.disp))))
},
_ => {
Ok(None)
},
}
} }
} }
} }

View file

@ -172,11 +172,31 @@ macro_rules! impl_vector {
} }
impl<S: BaseFloat> ApproxEq for $VectorN<S> { impl<S: BaseFloat> ApproxEq for $VectorN<S> {
type Epsilon = S; type Epsilon = S::Epsilon;
#[inline] #[inline]
fn approx_eq_eps(&self, other: &$VectorN<S>, epsilon: &S) -> bool { fn default_epsilon() -> S::Epsilon {
$(self.$field.approx_eq_eps(&other.$field, epsilon))&&+ S::default_epsilon()
}
#[inline]
fn default_max_relative() -> S::Epsilon {
S::default_max_relative()
}
#[inline]
fn default_max_ulps() -> u32 {
S::default_max_ulps()
}
#[inline]
fn relative_eq(&self, other: &Self, epsilon: S::Epsilon, max_relative: S::Epsilon) -> bool {
$(S::relative_eq(&self.$field, &other.$field, epsilon, max_relative))&&+
}
#[inline]
fn ulps_eq(&self, other: &Self, epsilon: S::Epsilon, max_ulps: u32) -> bool {
$(S::ulps_eq(&self.$field, &other.$field, epsilon, max_ulps))&&+
} }
} }

View file

@ -13,26 +13,27 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
extern crate cgmath; extern crate cgmath;
use cgmath::{Rad, Deg}; use cgmath::{Rad, Deg};
use cgmath::ApproxEq;
#[test] #[test]
fn conv() { fn conv() {
let angle: Rad<_> = Deg(-5.0f64).into(); let angle: Rad<_> = Deg(-5.0f64).into();
let angle: Deg<_> = angle.into(); let angle: Deg<_> = angle.into();
assert!(angle.approx_eq(&Deg(-5.0f64))); assert_ulps_eq!(&angle, &Deg(-5.0f64));
let angle: Rad<_> = Deg(30.0f64).into(); let angle: Rad<_> = Deg(30.0f64).into();
let angle: Deg<_> = angle.into(); let angle: Deg<_> = angle.into();
assert!(angle.approx_eq(&Deg(30.0f64))); assert_ulps_eq!(&angle, &Deg(30.0f64));
let angle: Deg<_> = Rad(-5.0f64).into(); let angle: Deg<_> = Rad(-5.0f64).into();
let angle: Rad<_> = angle.into(); let angle: Rad<_> = angle.into();
assert!(angle.approx_eq(&Rad(-5.0f64))); assert_ulps_eq!(&angle, &Rad(-5.0f64));
let angle: Deg<_> = Rad(30.0f64).into(); let angle: Deg<_> = Rad(30.0f64).into();
let angle: Rad<_> = angle.into(); let angle: Rad<_> = angle.into();
assert!(angle.approx_eq(&Rad(30.0f64))); assert_ulps_eq!(&angle, &Rad(30.0f64));
} }

View file

@ -1,41 +0,0 @@
// Copyright 2014 The CGMath Developers. For a full listing of the authors,
// refer to the Cargo.toml file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[macro_use]
extern crate cgmath;
use cgmath::*;
#[test]
fn macro_assert_approx_eq_eps() {
assert_approx_eq_eps!(1.0f64, 1.001, 0.01);
}
#[test]
#[should_panic]
fn macro_assert_approx_eq_eps_fail() {
assert_approx_eq_eps!(1.0f32, 1.02, 0.01);
}
#[test]
fn macro_assert_approx_eq() {
assert_approx_eq!(7.0f32 / 5.0, 1.4);
}
#[test]
#[should_panic]
fn macro_assert_approx_eq_fail() {
assert_approx_eq!(1.0f64 / 3.0, 0.333);
}

View file

@ -13,6 +13,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
#[macro_use] #[macro_use]
extern crate cgmath; extern crate cgmath;
@ -153,15 +155,15 @@ pub mod matrix2 {
fn test_from_angle() { fn test_from_angle() {
// Rotate the vector (1, 0) by π/2 radians to the vector (0, 1) // Rotate the vector (1, 0) by π/2 radians to the vector (0, 1)
let rot1 = Matrix2::from_angle(Rad(0.5f64 * f64::consts::PI)); let rot1 = Matrix2::from_angle(Rad(0.5f64 * f64::consts::PI));
assert_approx_eq!(rot1 * Vector2::unit_x(), &Vector2::unit_y()); assert_ulps_eq!(rot1 * Vector2::unit_x(), &Vector2::unit_y());
// Rotate the vector (-1, 0) by -π/2 radians to the vector (0, 1) // Rotate the vector (-1, 0) by -π/2 radians to the vector (0, 1)
let rot2 = -rot1; let rot2 = -rot1;
assert_approx_eq!(rot2 * -Vector2::unit_x(), &Vector2::unit_y()); assert_ulps_eq!(rot2 * -Vector2::unit_x(), &Vector2::unit_y());
// Rotate the vector (1, 1) by π radians to the vector (-1, -1) // Rotate the vector (1, 1) by π radians to the vector (-1, -1)
let rot3: Matrix2<f64> = Matrix2::from_angle(Rad(f64::consts::PI)); let rot3: Matrix2<f64> = Matrix2::from_angle(Rad(f64::consts::PI));
assert_approx_eq!(rot3 * Vector2::new(1.0, 1.0), &Vector2::new(-1.0, -1.0)); assert_ulps_eq!(rot3 * Vector2::new(1.0, 1.0), &Vector2::new(-1.0, -1.0));
} }
} }
@ -319,7 +321,7 @@ pub mod matrix3 {
fn check_from_axis_angle_x(pitch: Rad<f32>) { fn check_from_axis_angle_x(pitch: Rad<f32>) {
let found = Matrix3::from_angle_x(pitch); let found = Matrix3::from_angle_x(pitch);
let expected = Matrix3::from(Euler { x: pitch, y: Rad(0.0), z: Rad(0.0) }); let expected = Matrix3::from(Euler { x: pitch, y: Rad(0.0), z: Rad(0.0) });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_x(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_x(Rad(0.0)); }
@ -333,7 +335,7 @@ pub mod matrix3 {
fn check_from_axis_angle_y(yaw: Rad<f32>) { fn check_from_axis_angle_y(yaw: Rad<f32>) {
let found = Matrix3::from_angle_y(yaw); let found = Matrix3::from_angle_y(yaw);
let expected = Matrix3::from(Euler { x: Rad(0.0), y: yaw, z: Rad(0.0) }); let expected = Matrix3::from(Euler { x: Rad(0.0), y: yaw, z: Rad(0.0) });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_y(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_y(Rad(0.0)); }
@ -347,7 +349,7 @@ pub mod matrix3 {
fn check_from_axis_angle_z(roll: Rad<f32>) { fn check_from_axis_angle_z(roll: Rad<f32>) {
let found = Matrix3::from_angle_z(roll); let found = Matrix3::from_angle_z(roll);
let expected = Matrix3::from(Euler { x: Rad(0.0), y: Rad(0.0), z: roll }); let expected = Matrix3::from(Euler { x: Rad(0.0), y: Rad(0.0), z: roll });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_z(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_z(Rad(0.0)); }
@ -362,7 +364,7 @@ pub mod matrix3 {
fn check_from_axis_angle_x(pitch: Rad<f32>) { fn check_from_axis_angle_x(pitch: Rad<f32>) {
let found = Matrix3::from_axis_angle(Vector3::unit_x(), pitch); let found = Matrix3::from_axis_angle(Vector3::unit_x(), pitch);
let expected = Matrix3::from(Euler { x: pitch, y: Rad(0.0), z: Rad(0.0) }); let expected = Matrix3::from(Euler { x: pitch, y: Rad(0.0), z: Rad(0.0) });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_x(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_x(Rad(0.0)); }
@ -376,7 +378,7 @@ pub mod matrix3 {
fn check_from_axis_angle_y(yaw: Rad<f32>) { fn check_from_axis_angle_y(yaw: Rad<f32>) {
let found = Matrix3::from_axis_angle(Vector3::unit_y(), yaw); let found = Matrix3::from_axis_angle(Vector3::unit_y(), yaw);
let expected = Matrix3::from(Euler { x: Rad(0.0), y: yaw, z: Rad(0.0) }); let expected = Matrix3::from(Euler { x: Rad(0.0), y: yaw, z: Rad(0.0) });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_y(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_y(Rad(0.0)); }
@ -390,7 +392,7 @@ pub mod matrix3 {
fn check_from_axis_angle_z(roll: Rad<f32>) { fn check_from_axis_angle_z(roll: Rad<f32>) {
let found = Matrix3::from_axis_angle(Vector3::unit_z(), roll); let found = Matrix3::from_axis_angle(Vector3::unit_z(), roll);
let expected = Matrix3::from(Euler { x: Rad(0.0), y: Rad(0.0), z: roll }); let expected = Matrix3::from(Euler { x: Rad(0.0), y: Rad(0.0), z: roll });
assert_approx_eq_eps!(found, expected, 0.001); assert_relative_eq!(found, expected, epsilon = 0.001);
} }
#[test] fn test_zero() { check_from_axis_angle_z(Rad(0.0)); } #[test] fn test_zero() { check_from_axis_angle_z(Rad(0.0)); }
@ -407,10 +409,10 @@ pub mod matrix3 {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Matrix3::from(Euler::new(Deg(90.0), Deg(0.0), Deg(0.0))); let rot = Matrix3::from(Euler::new(Deg(90.0), Deg(0.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
let rot = Matrix3::from(Euler::new(Deg(-90.0), Deg(0.0), Deg(0.0))); let rot = Matrix3::from(Euler::new(Deg(-90.0), Deg(0.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -418,10 +420,10 @@ pub mod matrix3 {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(90.0), Deg(0.0))); let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(90.0), Deg(0.0)));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(-90.0), Deg(0.0))); let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(-90.0), Deg(0.0)));
assert_approx_eq!(vec3(-1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(-1.0, 0.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -429,10 +431,10 @@ pub mod matrix3 {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(0.0), Deg(90.0))); let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(0.0), Deg(90.0)));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(0.0), Deg(-90.0))); let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(0.0), Deg(-90.0)));
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
} }
@ -442,7 +444,7 @@ pub mod matrix3 {
let vec = vec3(0.0, 1.0, 0.0); let vec = vec3(0.0, 1.0, 0.0);
let rot = Matrix3::from(Euler::new(Deg(90.0), Deg(90.0), Deg(0.0))); let rot = Matrix3::from(Euler::new(Deg(90.0), Deg(90.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, 0.0, 1.0), rot * vec); assert_ulps_eq!(vec3(0.0, 0.0, 1.0), rot * vec);
} }
// tests that the Z rotation is done after the Y // tests that the Z rotation is done after the Y
@ -451,7 +453,7 @@ pub mod matrix3 {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(90.0), Deg(90.0))); let rot = Matrix3::from(Euler::new(Deg(0.0), Deg(90.0), Deg(90.0)));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
} }
} }
@ -464,7 +466,7 @@ pub mod matrix3 {
let rot = Matrix3::from_angle_x(Deg(90.0)); let rot = Matrix3::from_angle_x(Deg(90.0));
println!("x mat: {:?}", rot); println!("x mat: {:?}", rot);
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -472,7 +474,7 @@ pub mod matrix3 {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Matrix3::from_angle_y(Deg(90.0)); let rot = Matrix3::from_angle_y(Deg(90.0));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -480,7 +482,7 @@ pub mod matrix3 {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Matrix3::from_angle_z(Deg(90.0)); let rot = Matrix3::from_angle_z(Deg(90.0));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -488,7 +490,7 @@ pub mod matrix3 {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Matrix3::from_axis_angle(vec3(1.0, 1.0, 0.0).normalize(), Deg(90.0)); let rot = Matrix3::from_axis_angle(vec3(1.0, 1.0, 0.0).normalize(), Deg(90.0));
assert_approx_eq!(vec3(2.0f32.sqrt() / 2.0, -2.0f32.sqrt() / 2.0, 0.0), rot * vec); assert_ulps_eq!(vec3(2.0f32.sqrt() / 2.0, -2.0f32.sqrt() / 2.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -496,7 +498,7 @@ pub mod matrix3 {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Matrix3::from_axis_angle(vec3(0.0, 1.0, 1.0).normalize(), Deg(-90.0)); let rot = Matrix3::from_axis_angle(vec3(0.0, 1.0, 1.0).normalize(), Deg(-90.0));
assert_approx_eq!(vec3(0.0, -2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0), rot * vec); assert_ulps_eq!(vec3(0.0, -2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0), rot * vec);
} }
#[test] #[test]
@ -504,7 +506,7 @@ pub mod matrix3 {
let vec = vec3(0.0, 1.0, 0.0); let vec = vec3(0.0, 1.0, 0.0);
let rot = Matrix3::from_axis_angle(vec3(1.0, 0.0, 1.0).normalize(), Deg(90.0)); let rot = Matrix3::from_axis_angle(vec3(1.0, 0.0, 1.0).normalize(), Deg(90.0));
assert_approx_eq!(vec3(-2.0f32.sqrt() / 2.0, 0.0, 2.0f32.sqrt() / 2.0), rot * vec); assert_ulps_eq!(vec3(-2.0f32.sqrt() / 2.0, 0.0, 2.0f32.sqrt() / 2.0), rot * vec);
} }
} }
} }
@ -643,11 +645,11 @@ pub mod matrix4 {
fn test_invert() { fn test_invert() {
assert!(Matrix4::<f64>::identity().invert().unwrap().is_identity()); assert!(Matrix4::<f64>::identity().invert().unwrap().is_identity());
assert!(C.invert().unwrap().approx_eq(&( assert_ulps_eq!(&C.invert().unwrap(), &(
Matrix4::new( 5.0f64, -4.0f64, 1.0f64, 0.0f64, Matrix4::new( 5.0f64, -4.0f64, 1.0f64, 0.0f64,
-4.0f64, 8.0f64, -4.0f64, 0.0f64, -4.0f64, 8.0f64, -4.0f64, 0.0f64,
4.0f64, -8.0f64, 4.0f64, 8.0f64, 4.0f64, -8.0f64, 4.0f64, 8.0f64,
-3.0f64, 4.0f64, 1.0f64, -8.0f64) * 0.125f64))); -3.0f64, 4.0f64, 1.0f64, -8.0f64) * 0.125f64));
let mat_c = Matrix4::new(-0.131917f64, -0.76871f64, 0.625846f64, 0.0f64, let mat_c = Matrix4::new(-0.131917f64, -0.76871f64, 0.625846f64, 0.0f64,
-0., 0.631364f64, 0.775487f64, 0.0f64, -0., 0.631364f64, 0.775487f64, 0.0f64,
@ -714,7 +716,7 @@ pub mod matrix4 {
let matrix_long = Matrix3::from(quaternion); let matrix_long = Matrix3::from(quaternion);
let matrix_long = Matrix4::from(matrix_long); let matrix_long = Matrix4::from(matrix_long);
assert_approx_eq!(matrix_short, matrix_long); assert_ulps_eq!(matrix_short, matrix_long);
} }
} }
} }

View file

@ -13,11 +13,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
extern crate cgmath; extern crate cgmath;
use cgmath::{Point2, Point3}; use cgmath::{Point2, Point3};
use cgmath::ApproxEq;
macro_rules! impl_test_mul { macro_rules! impl_test_mul {
($PointN:ident { $($field:ident),+ }, $s:expr, $v:expr) => ( ($PointN:ident { $($field:ident),+ }, $s:expr, $v:expr) => (
@ -54,7 +54,7 @@ macro_rules! impl_test_rem {
#[test] #[test]
fn test_homogeneous() { fn test_homogeneous() {
let p = Point3::new(1.0f64, 2.0f64, 3.0f64); let p = Point3::new(1.0f64, 2.0f64, 3.0f64);
assert!(p.approx_eq(&Point3::from_homogeneous(p.to_homogeneous()))); assert_ulps_eq!(&p, &Point3::from_homogeneous(p.to_homogeneous()));
} }
#[test] #[test]
@ -74,4 +74,3 @@ fn test_rem() {
impl_test_rem!(Point3 { x, y, z }, 2.0f32, Point3::new(2.0f32, 4.0, 6.0)); impl_test_rem!(Point3 { x, y, z }, 2.0f32, Point3::new(2.0f32, 4.0, 6.0));
impl_test_rem!(Point2 { x, y }, 2.0f32, Point2::new(2.0f32, 4.0)); impl_test_rem!(Point2 { x, y }, 2.0f32, Point2::new(2.0f32, 4.0));
} }

View file

@ -13,6 +13,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
#[macro_use] #[macro_use]
extern crate cgmath; extern crate cgmath;
@ -58,7 +60,7 @@ mod to_from_euler {
use cgmath::*; use cgmath::*;
fn check_euler(rotation: Euler<Rad<f32>>) { fn check_euler(rotation: Euler<Rad<f32>>) {
assert_approx_eq_eps!(Euler::from(Quaternion::from(rotation)), rotation, 0.001); assert_relative_eq!(Euler::from(Quaternion::from(rotation)), rotation, epsilon = 0.001);
} }
const HPI: f32 = f32::consts::FRAC_PI_2; const HPI: f32 = f32::consts::FRAC_PI_2;
@ -84,7 +86,7 @@ mod from {
let matrix3 = Matrix3::from(Euler { x: x, y: y, z: z }); let matrix3 = Matrix3::from(Euler { x: x, y: y, z: z });
let quaternion = Quaternion::from(matrix3); let quaternion = Quaternion::from(matrix3);
let quaternion_matrix3 = Matrix3::from(quaternion); let quaternion_matrix3 = Matrix3::from(quaternion);
assert_approx_eq!(matrix3, quaternion_matrix3); assert_ulps_eq!(matrix3, quaternion_matrix3);
} }
// triggers: trace >= S::zero() // triggers: trace >= S::zero()
@ -120,7 +122,7 @@ mod arc {
fn test(src: Vector3<f32>, dst: Vector3<f32>) { fn test(src: Vector3<f32>, dst: Vector3<f32>) {
let q = Quaternion::from_arc(src, dst, None); let q = Quaternion::from_arc(src, dst, None);
let v = q.rotate_vector(src); let v = q.rotate_vector(src);
assert_approx_eq!(v.normalize(), dst.normalize()); assert_ulps_eq!(v.normalize(), dst.normalize());
} }
#[test] #[test]
@ -145,7 +147,7 @@ mod arc {
fn test_ortho() { fn test_ortho() {
let q: Quaternion<f32> = Quaternion::from_arc(Vector3::unit_x(), Vector3::unit_y(), None); let q: Quaternion<f32> = Quaternion::from_arc(Vector3::unit_x(), Vector3::unit_y(), None);
let q2 = Quaternion::from_axis_angle(Vector3::unit_z(), Rad::turn_div_4()); let q2 = Quaternion::from_axis_angle(Vector3::unit_z(), Rad::turn_div_4());
assert_approx_eq!(q, q2); assert_ulps_eq!(q, q2);
} }
} }
@ -157,10 +159,10 @@ mod rotate_from_euler {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from(Euler::new(Deg(90.0), Deg(0.0), Deg(0.0))); let rot = Quaternion::from(Euler::new(Deg(90.0), Deg(0.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
let rot = Quaternion::from(Euler::new(Deg(-90.0), Deg(0.0), Deg(0.0))); let rot = Quaternion::from(Euler::new(Deg(-90.0), Deg(0.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -168,10 +170,10 @@ mod rotate_from_euler {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(90.0), Deg(0.0))); let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(90.0), Deg(0.0)));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(-90.0), Deg(0.0))); let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(-90.0), Deg(0.0)));
assert_approx_eq!(vec3(-1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(-1.0, 0.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -179,10 +181,10 @@ mod rotate_from_euler {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(0.0), Deg(90.0))); let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(0.0), Deg(90.0)));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(0.0), Deg(-90.0))); let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(0.0), Deg(-90.0)));
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
} }
@ -192,7 +194,7 @@ mod rotate_from_euler {
let vec = vec3(0.0, 1.0, 0.0); let vec = vec3(0.0, 1.0, 0.0);
let rot = Quaternion::from(Euler::new(Deg(90.0), Deg(90.0), Deg(0.0))); let rot = Quaternion::from(Euler::new(Deg(90.0), Deg(90.0), Deg(0.0)));
assert_approx_eq!(vec3(0.0, 0.0, 1.0), rot * vec); assert_ulps_eq!(vec3(0.0, 0.0, 1.0), rot * vec);
} }
// tests that the Z rotation is done after the Y // tests that the Z rotation is done after the Y
@ -201,7 +203,7 @@ mod rotate_from_euler {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(90.0), Deg(90.0))); let rot = Quaternion::from(Euler::new(Deg(0.0), Deg(90.0), Deg(90.0)));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
} }
} }
@ -213,7 +215,7 @@ mod rotate_from_axis_angle {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from_angle_x(Deg(90.0)); let rot = Quaternion::from_angle_x(Deg(90.0));
assert_approx_eq!(vec3(0.0, -1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, -1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -221,7 +223,7 @@ mod rotate_from_axis_angle {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from_angle_y(Deg(90.0)); let rot = Quaternion::from_angle_y(Deg(90.0));
assert_approx_eq!(vec3(1.0, 0.0, 0.0), rot * vec); assert_ulps_eq!(vec3(1.0, 0.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -229,7 +231,7 @@ mod rotate_from_axis_angle {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Quaternion::from_angle_z(Deg(90.0)); let rot = Quaternion::from_angle_z(Deg(90.0));
assert_approx_eq!(vec3(0.0, 1.0, 0.0), rot * vec); assert_ulps_eq!(vec3(0.0, 1.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -237,7 +239,7 @@ mod rotate_from_axis_angle {
let vec = vec3(0.0, 0.0, 1.0); let vec = vec3(0.0, 0.0, 1.0);
let rot = Quaternion::from_axis_angle(vec3(1.0, 1.0, 0.0).normalize(), Deg(90.0)); let rot = Quaternion::from_axis_angle(vec3(1.0, 1.0, 0.0).normalize(), Deg(90.0));
assert_approx_eq!(vec3(2.0f32.sqrt() / 2.0, -2.0f32.sqrt() / 2.0, 0.0), rot * vec); assert_ulps_eq!(vec3(2.0f32.sqrt() / 2.0, -2.0f32.sqrt() / 2.0, 0.0), rot * vec);
} }
#[test] #[test]
@ -245,7 +247,7 @@ mod rotate_from_axis_angle {
let vec = vec3(1.0, 0.0, 0.0); let vec = vec3(1.0, 0.0, 0.0);
let rot = Quaternion::from_axis_angle(vec3(0.0, 1.0, 1.0).normalize(), Deg(-90.0)); let rot = Quaternion::from_axis_angle(vec3(0.0, 1.0, 1.0).normalize(), Deg(-90.0));
assert_approx_eq!(vec3(0.0, -2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0), rot * vec); assert_ulps_eq!(vec3(0.0, -2.0f32.sqrt() / 2.0, 2.0f32.sqrt() / 2.0), rot * vec);
} }
#[test] #[test]
@ -253,6 +255,6 @@ mod rotate_from_axis_angle {
let vec = vec3(0.0, 1.0, 0.0); let vec = vec3(0.0, 1.0, 0.0);
let rot = Quaternion::from_axis_angle(vec3(1.0, 0.0, 1.0).normalize(), Deg(90.0)); let rot = Quaternion::from_axis_angle(vec3(1.0, 0.0, 1.0).normalize(), Deg(90.0));
assert_approx_eq!(vec3(-2.0f32.sqrt() / 2.0, 0.0, 2.0f32.sqrt() / 2.0), rot * vec); assert_ulps_eq!(vec3(-2.0f32.sqrt() / 2.0, 0.0, 2.0f32.sqrt() / 2.0), rot * vec);
} }
} }

View file

@ -13,7 +13,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
extern crate cgmath; extern crate cgmath;
#[cfg(feature = "eders")] #[cfg(feature = "eders")]
@ -31,7 +32,7 @@ fn test_invert() {
}; };
let ti = t.inverse_transform().expect("Expected successful inversion"); let ti = t.inverse_transform().expect("Expected successful inversion");
let vt = t.transform_vector(v); let vt = t.transform_vector(v);
assert!(v.approx_eq(&ti.transform_vector(vt))); assert_ulps_eq!(&v, &ti.transform_vector(vt));
} }
#[test] #[test]
@ -42,7 +43,7 @@ fn test_look_at() {
let t: Decomposed<Vector3<f64>, Quaternion<f64>> = Transform::look_at(eye, center, up); let t: Decomposed<Vector3<f64>, Quaternion<f64>> = Transform::look_at(eye, center, up);
let point = Point3::new(1.0f64, 0.0, 0.0); let point = Point3::new(1.0f64, 0.0, 0.0);
let view_point = Point3::new(0.0f64, 1.0, 5.0); let view_point = Point3::new(0.0f64, 1.0, 5.0);
assert!(t.transform_point(point).approx_eq(&view_point)); assert_ulps_eq!(&t.transform_point(point), &view_point);
} }
#[cfg(feature = "eders")] #[cfg(feature = "eders")]
@ -57,5 +58,5 @@ fn test_serialize() {
let serialized = serde_json::to_string(&t).unwrap(); let serialized = serde_json::to_string(&t).unwrap();
let deserialized: Decomposed<Vector3<f64>, Quaternion<f64>> = serde_json::from_str(&serialized).unwrap(); let deserialized: Decomposed<Vector3<f64>, Quaternion<f64>> = serde_json::from_str(&serialized).unwrap();
assert!(t.approx_eq(&deserialized)); assert_ulps_eq!(&t, &deserialized);
} }

View file

@ -13,6 +13,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#[macro_use]
extern crate approx;
#[macro_use] #[macro_use]
extern crate cgmath; extern crate cgmath;
@ -229,30 +231,30 @@ mod test_magnitude {
#[test] #[test]
fn test_angle() { fn test_angle() {
assert!(Vector2::new(1.0f64, 0.0f64).angle(Vector2::new(0.0f64, 1.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector2::new(1.0f64, 0.0f64).angle(Vector2::new(0.0f64, 1.0f64)), &Rad(f64::consts::FRAC_PI_2));
assert!(Vector2::new(10.0f64, 0.0f64).angle(Vector2::new(0.0f64, 5.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector2::new(10.0f64, 0.0f64).angle(Vector2::new(0.0f64, 5.0f64)), &Rad(f64::consts::FRAC_PI_2));
assert!(Vector2::new(-1.0f64, 0.0f64).angle(Vector2::new(0.0f64, 1.0f64)).approx_eq( &-Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector2::new(-1.0f64, 0.0f64).angle(Vector2::new(0.0f64, 1.0f64)), &-Rad(f64::consts::FRAC_PI_2));
assert!(Vector3::new(1.0f64, 0.0f64, 1.0f64).angle(Vector3::new(1.0f64, 1.0f64, 0.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_3) )); assert_ulps_eq!(Vector3::new(1.0f64, 0.0f64, 1.0f64).angle(Vector3::new(1.0f64, 1.0f64, 0.0f64)), &Rad(f64::consts::FRAC_PI_3));
assert!(Vector3::new(10.0f64, 0.0f64, 10.0f64).angle(Vector3::new(5.0f64, 5.0f64, 0.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_3) )); assert_ulps_eq!(Vector3::new(10.0f64, 0.0f64, 10.0f64).angle(Vector3::new(5.0f64, 5.0f64, 0.0f64)), &Rad(f64::consts::FRAC_PI_3));
assert!(Vector3::new(-1.0f64, 0.0f64, -1.0f64).angle(Vector3::new(1.0f64, -1.0f64, 0.0f64)).approx_eq( &Rad(2.0f64 * f64::consts::FRAC_PI_3) )); assert_ulps_eq!(Vector3::new(-1.0f64, 0.0f64, -1.0f64).angle(Vector3::new(1.0f64, -1.0f64, 0.0f64)), &Rad(2.0f64 * f64::consts::FRAC_PI_3));
assert!(Vector4::new(1.0f64, 0.0f64, 1.0f64, 0.0f64).angle(Vector4::new(0.0f64, 1.0f64, 0.0f64, 1.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector4::new(1.0f64, 0.0f64, 1.0f64, 0.0f64).angle(Vector4::new(0.0f64, 1.0f64, 0.0f64, 1.0f64)), &Rad(f64::consts::FRAC_PI_2));
assert!(Vector4::new(10.0f64, 0.0f64, 10.0f64, 0.0f64).angle(Vector4::new(0.0f64, 5.0f64, 0.0f64, 5.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector4::new(10.0f64, 0.0f64, 10.0f64, 0.0f64).angle(Vector4::new(0.0f64, 5.0f64, 0.0f64, 5.0f64)), &Rad(f64::consts::FRAC_PI_2));
assert!(Vector4::new(-1.0f64, 0.0f64, -1.0f64, 0.0f64).angle(Vector4::new(0.0f64, 1.0f64, 0.0f64, 1.0f64)).approx_eq( &Rad(f64::consts::FRAC_PI_2) )); assert_ulps_eq!(Vector4::new(-1.0f64, 0.0f64, -1.0f64, 0.0f64).angle(Vector4::new(0.0f64, 1.0f64, 0.0f64, 1.0f64)), &Rad(f64::consts::FRAC_PI_2));
} }
#[test] #[test]
fn test_normalize() { fn test_normalize() {
// TODO: test normalize_to, normalize_sel.0, and normalize_self_to // TODO: test normalize_to, normalize_sel.0, and normalize_self_to
assert!(Vector2::new(3.0f64, 4.0f64).normalize().approx_eq( &Vector2::new(3.0/5.0, 4.0/5.0) )); assert_ulps_eq!(Vector2::new(3.0f64, 4.0f64).normalize(), &Vector2::new(3.0/5.0, 4.0/5.0));
assert!(Vector3::new(2.0f64, 3.0f64, 6.0f64).normalize().approx_eq( &Vector3::new(2.0/7.0, 3.0/7.0, 6.0/7.0) )); assert_ulps_eq!(Vector3::new(2.0f64, 3.0f64, 6.0f64).normalize(), &Vector3::new(2.0/7.0, 3.0/7.0, 6.0/7.0));
assert!(Vector4::new(1.0f64, 2.0f64, 4.0f64, 10.0f64).normalize().approx_eq( &Vector4::new(1.0/11.0, 2.0/11.0, 4.0/11.0, 10.0/11.0) )); assert_ulps_eq!(Vector4::new(1.0f64, 2.0f64, 4.0f64, 10.0f64).normalize(), &Vector4::new(1.0/11.0, 2.0/11.0, 4.0/11.0, 10.0/11.0));
} }
#[test] #[test]
fn test_cast() { fn test_cast() {
assert_approx_eq!(Vector2::new(0.9f64, 1.5).cast(), Vector2::new(0.9f32, 1.5)); assert_ulps_eq!(Vector2::new(0.9f64, 1.5).cast(), Vector2::new(0.9f32, 1.5));
assert_approx_eq!(Vector3::new(1.0f64, 2.4, -3.13).cast(), Vector3::new(1.0f32, 2.4, -3.13)); assert_ulps_eq!(Vector3::new(1.0f64, 2.4, -3.13).cast(), Vector3::new(1.0f32, 2.4, -3.13));
assert_approx_eq!(Vector4::new(13.5f64, -4.6, -8.3, 2.41).cast(), Vector4::new(13.5f32, -4.6, -8.3, 2.41)); assert_ulps_eq!(Vector4::new(13.5f64, -4.6, -8.3, 2.41).cast(), Vector4::new(13.5f32, -4.6, -8.3, 2.41));
} }