#macro-derive #extend #partial-eq #field #debugging #approx #epsilon

approx-derive

通过 derive 宏扩展 approx 库

4 个版本

0.1.3 2024年5月13日
0.1.2 2024年5月12日
0.1.1 2024年5月4日
0.1.0 2024年5月4日

#18#extend

Apache-2.0

30KB
482

Apache License Test Crate Crates.io Total Downloads

approx_derive

approx-derive 通过两个 derive 宏 AbsDiffEqRelativeEq 扩展了流行的 approx 库。这允许快速为这些类型生成与 approx 库中提供的宏一起使用的实现。

文档

访问 docs.rs 查看文档。


lib.rs:

此库为 AbsDiffEqRelativeEq 特性提供了 derive 宏。

这些 derive 宏仅实现了两种特性,使用 ...<Rhs = Self>。宏通过查看第一个结构体字段或用户指定的任何类型来推断 [AbsDiffEq] 特性的 EPSILON 类型。

以下示例解释了一个可能的用例。

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);

在这种情况下,生成的代码可能如下所示

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
        }
    }
};

AbsDiffEq derive 宏反复调用 abs_diff_eq 方法来检查所有字段是否匹配。

字段属性

跳过字段

有时,我们只想比较某些字段,而完全忽略其他字段。

#[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);

字段类型转换

由多个字段组成且具有不同数值类型的结构体,在没有额外提示的情况下无法推导。毕竟,我们应该指定如何处理这种类型不匹配。

#[derive(AbsDiffEq, PartialEq, Debug)]
struct MyStruct {
    v1: f32,
    v2: f64,
}

我们可以使用 #[approx(cast_field)]#[approx(cast_value)] 属性来实现这一目标。

#[derive(AbsDiffEq, PartialEq, Debug)]
struct MyStruct {
    v1: f32,
    #[approx(cast_field)]
    v2: f64,
}

现在第二个字段将被转换为推断的 epsilon 值的类型(f32)。我们可以通过测试是否通过此过程会丢失 f64::MIN_POSITIVE 的大小变化来检查这一点。

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);

静态值

我们可以为单个字段强制设置静态 EPSILONmax_relative 值。

#[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);

结构体属性

默认 Epsilon

【AbsDiffEq】特性和质允许为其关联的 EPSILON 类型指定默认值。我们可以在结构体级别指定此值来控制此值。

#[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);

默认最大相对偏差

与【默认 Epsilon】类似,我们还可以选择默认的最大相对偏差。

#[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 类型

当不指定任何内容时,宏将从第一个结构体字段的类型推断 EPSILON 类型。在某些场景下,这可能会出现问题,这就是为什么我们还可以手动指定此类型。

#[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);

依赖关系

~270–720KB
~17K SLoC