7 releases
0.1.7 | Nov 1, 2024 |
---|---|
0.1.6 |
|
0.1.4 | Aug 26, 2024 |
0.1.3 | May 13, 2024 |
#367 in Procedural macros
972 downloads per month
36KB
538 lines
approx_derive
approx-derive
extends the popular approx
by two derive macros AbsDiffEq
and RelativeEq
.
This allows to quickly derive implementations for comparing these types with the macros provided in
approx
crate.
Documentation
Visit docs.rs to view the documentation.
lib.rs
:
This crate provides derive macros for the AbsDiffEq and RelativeEq traits of the approx crate.
These derive macros only implement both traits with ...<Rhs = Self>
.
The macros infer the EPSILON
type of the [AbsDiffEq] trait by looking
at the type of the first struct field or any type specified by the user.
This table lists all attributes which can be used to customize the derived traits.
They are ordered in descending priority, meaning setting the #[approx(equal)]
will overwrite
any specifications made in the #[approx(map = ...)]
attribute.
Field Attribute | Functionality |
---|---|
#[approx(skip)] |
Skips the field entirely |
#[approx(equal)] |
Checks this field with == for Equality |
#[approx(cast_field)] |
Casts the field with .. as .. syntax. |
#[approx(map = ..)] |
Maps values before comparing them. |
#[approx(static_epsilon = ..)] |
Defines a static epsilon value for this particular field. |
Struct Attribute | |
#[approx(default_epsilon = ...)] |
Sets the default epsilon value |
#[approx(default_max_relative = ...)] |
Sets the default max_relative value. |
#[approx(epsilon_type = ...)] |
Sets the type of the epsilon value |
The following example explains a possible use-case.
use approx_derive::AbsDiffEq;
// Define a new type and derive the AbsDiffEq trait
#[derive(AbsDiffEq, PartialEq, Debug)]
struct Position {
x: f64,
y: f64
}
// Compare if two given positions match
// with respect to geiven epsilon.
let p1 = Position { x: 1.01, y: 2.36 };
let p2 = Position { x: 0.99, y: 2.38 };
approx::assert_abs_diff_eq!(p1, p2, epsilon = 0.021);
In this case, the generated code looks something like this:
const _ : () =
{
#[automatically_derived] impl approx :: AbsDiffEq for Position
{
type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
fn default_epsilon() -> Self :: Epsilon {
<f64 as approx::AbsDiffEq>::default_epsilon()
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
<f64 as approx::AbsDiffEq>::abs_diff_eq(
&self.x,
& other.x,
epsilon.clone()
) &&
<f64 as approx::AbsDiffEq>::abs_diff_eq(
&self.y,
&other.y,
epsilon.clone()
) && true
}
}
};
The [AbsDiffEq] derive macro calls the abs_diff_eq
method repeatedly on all fields
to determine if all are matching.
Field Attributes
Skipping Fields
Sometimes, we only want to compare certain fields and omit others completely.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct Player {
hit_points: f32,
pos_x: f32,
pos_y: f32,
#[approx(skip)]
id: (usize, usize),
}
let player1 = Player {
hit_points: 100.0,
pos_x: 2.0,
pos_y: -650.345,
id: (0, 1),
};
let player2 = Player {
hit_points: 99.9,
pos_x: 2.001,
pos_y: -649.898,
id: (22, 0),
};
approx::assert_abs_diff_eq!(player1, player2, epsilon = 0.5);
Testing for Equality
When identical equality is desired, we can specify this with the #[approx(equal)]
attribute.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct Prediction {
confidence: f64,
#[approx(equal)]
category: String,
}
Note that in this case, the type of the epsilon value for the implementation of
AbsDiffEq is inferred from the
first field of the Prediction
struct.
This means if we reorder the arguments of the struct, we need to manually set the epsilon type.
#[derive(AbsDiffEq, PartialEq, Debug)]
#[approx(epsilon_type = f64)]
struct Prediction {
#[approx(equal)]
category: String,
confidence: f64,
}
Casting Fields
Structs which consist of multiple fields with different numeric types, can not be derived without additional hints. After all, we should specify how this type mismatch will be handled.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct MyStruct {
v1: f32,
v2: f64,
}
We can use the #[approx(cast_field)]
and #[approx(cast_value)]
attributes to achieve this goal.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct MyStruct {
v1: f32,
#[approx(cast_field)]
v2: f64,
}
Now the second field will be casted to the type of the inferred epsilon value (f32
).
We can check this by testing if a change in the size of f64::MIN_POSITIVE
would get lost by
this procedure.
let ms1 = MyStruct {
v1: 1.0,
v2: 3.0,
};
let ms2 = MyStruct {
v1: 1.0,
v2: 3.0 + f64::MIN_POSITIVE,
};
approx::assert_relative_eq!(ms1, ms2);
Mapping Values
We can map values before comparing them.
By default, we need to return an option of the value in question.
This allows to do computations where error can occur.
Although this error is not caught, the comparison will fail if any of the two compared objects
return a None
value.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct Tower {
height_in_meters: f32,
#[approx(map = |x: &f32| Some(x.sqrt()))]
area_in_meters_squared: f32,
}
This functionality can also be useful when having more complex datatypes.
#[derive(PartialEq, Debug)]
enum Time {
Years(u16),
Months(u16),
Weeks(u16),
Days(u16),
}
fn time_to_days(time: &Time) -> Option<u16> {
match time {
Time::Years(y) => Some(365 * y),
Time::Months(m) => Some(30 * m),
Time::Weeks(w) => Some(7 * w),
Time::Days(d) => Some(*d),
}
}
#[derive(AbsDiffEq, PartialEq, Debug)]
#[approx(epsilon_type = u16)]
struct Dog {
#[approx(map = time_to_days)]
age: Time,
#[approx(map = time_to_days)]
next_doctors_appointment: Time,
}
Static Values
We can force a static EPSILON
or max_relative
value for individual fields.
#[derive(AbsDiffEq, PartialEq, Debug)]
struct Rectangle {
#[approx(static_epsilon = 5e-2)]
a: f64,
b: f64,
#[approx(static_epsilon = 7e-2)]
c: f64,
}
let r1 = Rectangle {
a: 100.01,
b: 40.0001,
c: 30.055,
};
let r2 = Rectangle {
a: 99.97,
b: 40.0005,
c: 30.049,
};
// This is always true although the epsilon is smaller than the
// difference between fields a and b respectively.
approx::assert_abs_diff_eq!(r1, r2, epsilon = 1e-1);
approx::assert_abs_diff_eq!(r1, r2, epsilon = 1e-2);
approx::assert_abs_diff_eq!(r1, r2, epsilon = 1e-3);
// Here, the epsilon value has become larger than the difference between the
// b field values.
approx::assert_abs_diff_ne!(r1, r2, epsilon = 1e-4);
Struct Attributes
Default Epsilon
The [AbsDiffEq] trait allows to specify a default value for its EPSILON
associated type.
We can control this value by specifying it on a struct level.
#[derive(AbsDiffEq, PartialEq, Debug)]
#[approx(default_epsilon = 10)]
struct Benchmark {
cycles: u64,
warm_up: u64,
}
let benchmark1 = Benchmark {
cycles: 248,
warm_up: 36,
};
let benchmark2 = Benchmark {
cycles: 239,
warm_up: 28,
};
// When testing with not additional arguments, the results match
approx::assert_abs_diff_eq!(benchmark1, benchmark2);
// Once we specify a lower epsilon, the values do not agree anymore.
approx::assert_abs_diff_ne!(benchmark1, benchmark2, epsilon = 5);
Default Max Relative
Similarly to [Default Epsilon], we can also choose a default max_relative devaition.
#[derive(RelativeEq, PartialEq, Debug)]
#[approx(default_max_relative = 0.1)]
struct Benchmark {
time: f32,
warm_up: f32,
}
let bench1 = Benchmark {
time: 3.502785781,
warm_up: 0.58039458,
};
let bench2 = Benchmark {
time: 3.7023458,
warm_up: 0.59015897,
};
approx::assert_relative_eq!(bench1, bench2);
approx::assert_relative_ne!(bench1, bench2, max_relative = 0.05);
Epsilon Type
When specifying nothing, the macros will infer the EPSILON
type from the type of the
first struct field.
This can be problematic in certain scenarios which is why we can also manually specify this
type.
#[derive(RelativeEq, PartialEq, Debug)]
#[approx(epsilon_type = f32)]
struct Car {
#[approx(cast_field)]
produced_year: u32,
horse_power: f32,
}
let car1 = Car {
produced_year: 1992,
horse_power: 122.87,
};
let car2 = Car {
produced_year: 2000,
horse_power: 117.45,
};
approx::assert_relative_eq!(car1, car2, max_relative = 0.05);
approx::assert_relative_ne!(car1, car2, max_relative = 0.01);
Dependencies
~220–660KB
~16K SLoC