Code Monkey home page Code Monkey logo

float_eq-rs's Introduction

float_eq

Build codecov crate documentation

Compare IEEE floating point primitives, structs and collections for equality.

This crate provides an API with a focus on making the choices of comparison algorithm(s) and tolerances intuitive to implementers and maintainers, and of providing clear output for debugging and development iteration.

This readme is a quick tour of the crate. For introductory material, guides and discussion see the float_eq guide.

Usage

Add this to your cargo.toml:

[dependencies]
float_eq = "1"

And, if you're using the 2015 edition, this to your crate root:

extern crate float_eq;

Then, you can import items with use:

use float_eq::{assert_float_eq, float_eq};

Comparisons

This crate provides boolean comparison operations:

if (float_eq!(y_pos, 0.0, abs <= 0.000_1)) {
    //...
}

And asserts:

const RECIP_REL_TOL: f32 = 0.000_366_210_94;
assert_float_eq!(x.recip(), 10.0, r2nd <= RECIP_REL_TOL);

Using absolute tolerance, relative tolerance or ULPs based comparison algorithms.

Composite types

Composite types may implement the provided extension traits to be compared on a field-by-field basis:

let a = Complex32 { re: 2.0, im: 4.000_002 };
let b = Complex32 { re: 2.000_000_5, im: 4.0 };

assert_float_eq!(a, b, ulps <= ComplexUlps32 { re: 2, im: 4 });

...and if they are homogeneous, with a uniformly applied tolerance across all fields:

assert_float_eq!(a, b, ulps_all <= 4);

Arrays of any size are supported:

let a = [1.0, -2.0, 3.0];
let b = [-1.0, 2.0, 3.5];
assert_float_eq!(a, b, abs <= [2.0, 4.0, 0.5]);
assert_float_eq!(a, b, abs_all <= 4.0);

As are tuples up to size 12 (inclusive):

let a = (1.0f32, 2.0f64);
let b = (1.5f32, -2.0f64);
assert_float_eq!(a, b, r2nd <= (0.5, 2.0));

Many standard and core types like Vec are supported:

let a = vec![1.0, -2.0, 3.0];
let b = vec![-1.0, 2.0, 3.5];
assert_float_eq!(a, b, rmax <= vec![2.0, 2.0, 0.25]);
assert_float_eq!(a, b, rmax_all <= 2.0);

There are blanket trait impls for comparing mutable and immutable reference types, the contents of Cell, RefCell, Rc, Arc and Box instances, as well as for slices, Option, Vec, VecDeque, LinkedList, BTreeMap and HashMap.

Derivable

The extension traits may be derived for non-generic structs and tuple structs:

#[derive_float_eq(
    ulps_tol = "PointUlps",
    ulps_tol_derive = "Clone, Copy, Debug, PartialEq",
    debug_ulps_diff = "PointUlpsDebugUlpsDiff",
    debug_ulps_diff_derive = "Clone, Copy, Debug, PartialEq",
    all_tol = "f64"
)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

let a = Point { x: 1.0, y: -2.0 };
let c = Point { 
    x: 1.000_000_000_000_000_9, 
    y: -2.000_000_000_000_001_3
};
assert_float_eq!(a, c, ulps <= PointUlps { x: 4, y: 3 });
assert_float_eq!(a, c, ulps_all <= 4);

Error messages

Asserts provide additional useful context information. For example:

assert_float_eq!(4.0f32, 4.000_008, rmax <= 0.000_001);

Panics with this error message:

thread 'main' panicked at 'assertion failed: `float_eq!(left, right, rmax <= t)`
        left: `4.0`,
       right: `4.000008`,
    abs_diff: `0.000008106232`,
   ulps_diff: `Some(17)`,
    [rmax] t: `0.000004000008`', assert_failure.rs:15:5

Where [rmax] t shows the tolerance value that the absolute difference was compared against after being appropriately scaled.

Optional features

This crate can be used without the standard library (#![no_std]) by disabling the default std feature. Use this in Cargo.toml:

[dependencies.float_eq]
version = "1"
default-features = false

Other optional features:

  • derive โ€” provides custom derive macros for all traits.
  • num โ€” blanket trait impls for num::Complex where it is instanced with a compatible type.

Related efforts

The approx, float-cmp, assert_float_eq and is_close crates provide similar floating point comparison capabilities to float_eq. The almost crate divides its API into comparison of floats against zero and non-zero values. The efloat crate provides an f32 equivalent type that tracks the maximum possible error bounds that may have occured due to rounding.

The ieee754 crate is not a comparison library, but provides useful functionality for decomposing floats into their component parts, iterating over representable values and working with ULPs directly, amoung other things.

Contributing

Constructive feedback, suggestions and contributions welcomed, please open an issue.

Changelog

Release information is available in CHANGELOG.md.


License

Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in float_eq by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

float_eq-rs's People

Contributors

bcliden avatar jtempest avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

float_eq-rs's Issues

Ulps types are poorly specified for composite types

The library needs to decide whether Ulps types are fully fledged types intended for numeric manipulation, or if they are strictly debugging aids. For example, the MyComplex32 examples don't take into account what happens if one or more members have a valid difference in ULPs but the others do not. For example, given this call:

MyComplex32 { re: 1.0, im: 2.0 }.ulps_diff(&MyComplex32 { re: 1.0, im: -2.0 })

Should the result be None, or MyComplex32 { re: Some(0), im: None }. The former would appear to be more useful from a numerical algorithm standpoint, and the latter from a debugging standpoint. Given that float_eq is primarily a debugging aid (and FloatDiff already leans this way), I think it should be explicit about the diffs being for debugging, and thus more granularly specified.

Add unit tests to FloatEqDebug implementations

Specifically, it looks like the missing elements in the code coverage are from the array implementation, but the output should probably have tests applied to it more generally. It's also probably worth considering the tests for assert failure more generally, and see if there's corner cases missing.

Additional relative checks

Currently the relative check rel/rel_all uses the precision of the largest value being compared. For completeness, and to enable more control over unit tests, this will be expanded to include the following types of relative check for tests:

  • rmax - relative to the precision of max(a, b) (an alias of rel, for reasons of compatibility/expectation)
  • rmin - relative to the precision of min(a, b)
  • r1st - relative to the precision of a
  • r2nd - relative to the precision of b

Implement traits over standard Rust types

Implement FloatEq, FloatDiff, FloatEqDebug and where appropriate FloatEqAll and FloatEqAllDebug over the following standard types (see #3):

  • f32
  • f64
  • Reference types
  • Arrays (size 0 to 32, inclusive)
  • Tuples (including (), up to size 12, inclusive)

Wrappers

  • Cell
  • RefCell
  • Box
  • Rc
  • Arc

Containers that may fail calculating diffs

  • Option (see #12)
  • Result (postponed, possibly indefinitely, pending design of what to do with enums)
  • Slices (structured epsilons - see #13)
  • Slices (*_all checks)
  • Vec
  • VecDeque
  • LinkedList
  • BTreeMap
  • HashMap (maybe approximately eq values for exact key matches?)
  • BTreeSet (requires that types be Ord, and Rust floats are not)
  • HashSet (requires that types be Eq/Hash, and Rust floats are neither)
  • TODO: Containers with other containers (e.g. Vec with arrays, etc). (will be added as a fresh issue)

Derive FloatUlps

Part one of deriving traits:

#[derive(FloatUlps)]
struct Foo {
    a: f32,
    b: [f64; 2],
}

Should expand to:

struct __FloatUlps_Foo {
    a: Ulps<f32>,
    b: Ulps<[f64; 2]>,
}

impl float_eq::FloatUlps for Foo {
    type Ulps = __FloatUlps_Foo;
}

Check that this works for:

  • Empty tuple struct
  • Tuple struct
  • Named field struct
  • Struct with generic fields

Implement FloatEq over standard types

FloatEq is currently implemented over the following types:

  • f32
  • f64
  • [T; n] where T: FloatEq
  • num::Complex<T> where T: FloatEq (behind a feature flag)

It would be helpful to expand this to be implemented over a similar range of standard container types as PartialEq is, This is a list of the potentially relevant impls, using the PartialEq Implementors list as of version 1.43.1:

Special

impl PartialEq<!> for !

Reference types (immutable)

impl<'_, '_, A, B> PartialEq<&'_ B> for &'_ A where
    A: PartialEq<B> + ?Sized,
    B: ?Sized,

TODO: The corresponsing a == &b and &a == b are missing from PartialEq because of this issue, but do we have the same restraint? EDIT: yes.

Reference types (mutable)

impl<'_, '_, A, B> PartialEq<&'_ B> for &'_ mut A where
    A: PartialEq<B> + ?Sized,
    B: ?Sized, 
impl<'_, '_, A, B> PartialEq<&'_ mut B> for &'_ A where
    A: PartialEq<B> + ?Sized,
    B: ?Sized, 
impl<'_, '_, A, B> PartialEq<&'_ mut B> for &'_ mut A where
    A: PartialEq<B> + ?Sized,
    B: ?Sized,

Slices

impl<A, B> PartialEq<[B]> for [A] where
    A: PartialEq<B>, 

impl<const N: usize, A, B> PartialEq<[A; N]> for [B] where
    B: PartialEq<A>,
    [A; N]: LengthAtMost32, 
impl<'b, const N: usize, A, B> PartialEq<[A; N]> for &'b [B] where
    B: PartialEq<A>,
    [A; N]: LengthAtMost32, 
impl<'b, const N: usize, A, B> PartialEq<[A; N]> for &'b mut [B] where
    B: PartialEq<A>,
    [A; N]: LengthAtMost32, 

Arrays

impl<const N: usize, A, B> PartialEq<[B; N]> for [A; N] where
    A: PartialEq<B>,
    [A; N]: LengthAtMost32,
    [B; N]: LengthAtMost32, 

impl<const N: usize, A, B> PartialEq<[B]> for [A; N] where
    A: PartialEq<B>,
    [A; N]: LengthAtMost32, 
impl<'b, const N: usize, A, B> PartialEq<&'b [B]> for [A; N] where
    A: PartialEq<B>,
    [A; N]: LengthAtMost32, 
impl<'b, const N: usize, A, B> PartialEq<&'b mut [B]> for [A; N] where
    A: PartialEq<B>,
    [A; N]: LengthAtMost32, 

Tuples

Implementation across tuples is dependent on thinking more generally about how epsilon values should apply to heterogeneous types, and whether they ought to be supported at all.

impl PartialEq<()> for ()
impl<A> PartialEq<(A,)> for (A,) where
    A: PartialEq<A> + ?Sized, 
impl<A, B> PartialEq<(A, B)> for (A, B) where
    A: PartialEq<A>,
    B: PartialEq<B> + ?Sized, 
//...
impl<A, B, C, D, E, F, G, H, I, J, K, L> PartialEq<(A, B, C, D, E, F, G, H, I, J, K, L)> for (A, B, C, D, E, F, G, H, I, J, K, L) where
    A: PartialEq<A>,
    B: PartialEq<B>,
    C: PartialEq<C>,
    D: PartialEq<D>,
    E: PartialEq<E>,
    F: PartialEq<F>,
    G: PartialEq<G>,
    H: PartialEq<H>,
    I: PartialEq<I>,
    J: PartialEq<J>,
    K: PartialEq<K>,
    L: PartialEq<L> + ?Sized, 

Vec

impl<A, B> PartialEq<Vec<B>> for Vec<A> where
    A: PartialEq<B>, 

impl<'_, A, B> PartialEq<&'_ [B]> for Vec<A> where
    A: PartialEq<B>, 
impl<'_, A, B> PartialEq<&'_ mut [B]> for Vec<A> where
    A: PartialEq<B>, 
	
impl<const N: usize, A, B> PartialEq<[B; N]> for Vec<A> where
    A: PartialEq<B>,
    [B; N]: LengthAtMost32, 
impl<'_, const N: usize, A, B> PartialEq<&'_ [B; N]> for Vec<A> where
    A: PartialEq<B>,
    [B; N]: LengthAtMost32, 

TODO: Is impl<'_, const N: usize, A, B> PartialEq<&'_ mut [B; N]> for Vec<A> missing?

VecDeque

impl<A> PartialEq<VecDeque<A>> for VecDeque<A> where
    A: PartialEq<A>, 

impl<'_, A, B> PartialEq<&'_ [B]> for VecDeque<A> where
    A: PartialEq<B>, 
impl<'_, A, B> PartialEq<&'_ mut [B]> for VecDeque<A> where
    A: PartialEq<B>, 

impl<const N: usize, A, B> PartialEq<[B; N]> for VecDeque<A> where
    A: PartialEq<B>,
    [B; N]: LengthAtMost32, 
impl<'_, const N: usize, A, B> PartialEq<&'_ [B; N]> for VecDeque<A> where
    A: PartialEq<B>,
    [B; N]: LengthAtMost32, 
impl<'_, const N: usize, A, B> PartialEq<&'_ mut [B; N]> for VecDeque<A> where
    A: PartialEq<B>,
    [B; N]: LengthAtMost32, 

impl<A, B> PartialEq<Vec<B>> for VecDeque<A> where
    A: PartialEq<B>,

Set collections

TODO: I assume that HashSet is a bad target since it requires Eq, whereas BTrees might be more amenable.

impl<T, S> PartialEq<HashSet<T, S>> for HashSet<T, S> where
    S: BuildHasher,
    T: Eq + Hash,
impl<T> PartialEq<BTreeSet<T>> for BTreeSet<T> where
    T: PartialEq<T>, 

Map collections

TODO: I assume these should these be FloatEq over just their values, since they require Eq on keys.

impl<K, V, S> PartialEq<HashMap<K, V, S>> for HashMap<K, V, S> where
    K: Eq + Hash,
    S: BuildHasher,
    V: PartialEq<V>, 
impl<K, V> PartialEq<BTreeMap<K, V>> for BTreeMap<K, V> where
    K: PartialEq<K>,
    V: PartialEq<V>, 

Result

TODO: Should this just be over T: FloatEq, or should E also be involved?

impl<T, E> PartialEq<Result<T, E>> for Result<T, E> where
    E: PartialEq<E>,
    T: PartialEq<T>,

Cow

impl<'a, 'b, B, C> PartialEq<Cow<'b, C>> for Cow<'a, B> where
    B: PartialEq<C> + ToOwned + ?Sized,
    C: ToOwned + ?Sized, 

impl<'_, '_, A, B> PartialEq<&'_ [B]> for Cow<'_, [A]> where
    A: PartialEq<B>,
    A: Clone, 	
impl<'_, '_, A, B> PartialEq<&'_ mut [B]> for Cow<'_, [A]> where
    A: PartialEq<B> + Clone, 

impl<'_, A, B> PartialEq<Vec<B>> for Cow<'_, [A]> where
    A: PartialEq<B> + Clone, 

Simple inner type

impl<T> PartialEq<Option<T>> for Option<T> where
    T: PartialEq<T>, 
impl<T> PartialEq<Box<T>> for Box<T> where
    T: PartialEq<T> + ?Sized, 
impl<T> PartialEq<Cell<T>> for Cell<T> where
    T: PartialEq<T> + Copy, 
impl<T> PartialEq<RefCell<T>> for RefCell<T> where
    T: PartialEq<T> + ?Sized, 
impl<T> PartialEq<Rc<T>> for Rc<T> where
    T: PartialEq<T> + ?Sized, 
impl<T> PartialEq<Arc<T>> for Arc<T> where
    T: PartialEq<T> + ?Sized, 
impl<T> PartialEq<LinkedList<T>> for LinkedList<T> where
    T: PartialEq<T>, 
impl<P, Q> PartialEq<Pin<Q>> for Pin<P> where
    P: Deref,
    Q: Deref,
    <P as Deref>::Target: PartialEq<<Q as Deref>::Target>, 
impl<T> PartialEq<Bound<T>> for Bound<T> where
    T: PartialEq<T>, 
impl<T> PartialEq<Poll<T>> for Poll<T> where
    T: PartialEq<T>, 
impl<T> PartialEq<Reverse<T>> for Reverse<T> where
    T: PartialEq<T>, 
impl<T> PartialEq<ManuallyDrop<T>> for ManuallyDrop<T> where
    T: PartialEq<T> + ?Sized, 

Derived ulps_tol not usable in other modules because of private fields

Here is an adaptation of the code shown in the documentation, which illustrates the problem

mod foo {
    use float_eq::*;

    #[derive_float_eq(
        ulps_tol = "PointUlps",
        ulps_tol_derive = "Clone, Copy, Debug, PartialEq",
        debug_ulps_diff = "PointUlpsDebugUlpsDiff",
        debug_ulps_diff_derive = "Clone, Copy, Debug, PartialEq",
        all_tol = "f64"
    )]
    #[derive(Debug, PartialEq, Clone, Copy)]
    pub struct Point {
        pub x: f64,
        pub y: f64,
    }

    #[cfg(test)]
    #[test]
    fn test_same_module() {
        let a = Point { x: 1.0, y: -2.0 };
        let c = Point {
            x: 1.000_000_000_000_000_9,
            y: -2.000_000_000_000_001_3
        };
        assert_float_eq!(a, c, ulps <= PointUlps { x: 4, y: 3 }); // Works
        assert_float_eq!(a, c, ulps_all <= 4);
    }
}

#[cfg(test)]
mod tests {
    use super::foo::*;
    use float_eq::assert_float_eq;

    #[test]
    fn test_other_module() {
        let a = Point { x: 1.0, y: -2.0 };
        let c = Point {
            x: 1.000_000_000_000_000_9,
            y: -2.000_000_000_000_001_3
        };
        assert_float_eq!(a, c, ulps <= PointUlps { x: 4, y: 3 }); // ERROR: x and y private
        assert_float_eq!(a, c, ulps_all <= 4);
    }
}

Both foo::Point and its x and y fields are public, so they can be used in other modules. However, the x and y fields of the derived PointUlps are private, preventing them from being used in other modules, which makes the whole of PointUlps unusable.

Use with Complex<T> should be clarified

Problem

I need to compare Complex64 values and to assert their equality in unit tests.
I'm at a loss when I'm trying to use the macros with complex values. It looks like the documentation is incorrect, and that some items that should be public are actually not public.

Step

On https://crates.io/crates/float_eq, this example shows how to do the comparison by using the :

let a = Complex32 { re: 2.0, im: 4.000_002 };
let b = Complex32 { re: 2.000_000_5, im: 4.0 };

assert_float_eq!(a, b, ulps <= Complex32Ulps { re: 2, im: 4 });

(1) I couldn't find Complex32Ulps. However, there is a ComplexUlps32 so I assume it is a typo. This page suggests it is public as float_eq::ComplexUlps32. But when I try, it fails to compile because of an unresolved import.

(2) By looking at the source code in src/trait_impls/num_complex.rs, I see ComplexUlps32 is defined as public, but it doesn't look like it can be used outside of the crate (and I haven't seen any public import).

(3) I resorted to re-defining it myself. Also, it cannot be instantiated as a structure, so I used ComplexUlps32::new instead.

        use float_eq::{float_eq, assert_float_eq};
        type ComplexUlps32 = UlpsTol<Complex<f32>>;
        let a = Complex32 { re: 2.0, im: 4.000_002 };
        let b = Complex32 { re: 2.000_000_5, im: 4.0 };
        assert_float_eq!(a, b, ulps <= ComplexUlps32::new(2.0, 4.0));

but then I get this error:

error[E0277]: the trait bound `num::Complex<f32>: FloatEqUlpsTol` is not satisfied
   --> rpnc-lib\src\value.rs:398:40
    |
398 |         assert_float_eq!(a, b, ulps <= ComplexUlps32::new(2.0, 4.0));
    |                                        ^^^^^^^^^^^^^ the trait `FloatEqUlpsTol` is not implemented for `num::Complex<f32>`

I'll continue searching a little bit, but either I'm misusing it, or something is wrong in the crate.
How can one use assert_float_eq and float_eq with Complex<T>?

Version

float_eq = "1.0.0"
rust toolchain 1.62.0
used with Rust edition 2021

Consider taking `self` instead of `&self`

I wanted to look into implementing these traits for Iterators, but taking &self makes that a little difficult. With the current traits there isn't a convenient way to implement the following blanket impl:

impl<T: FloatDiff, I: IntoIterator<Item=T>> FloatDiff for I {
    type AbsDiff = AbsDiffIter<T::AbsDiff>;

    fn abs_diff(&self, other: &Self) -> Self::AbsDiff {
        /* can't call self.into_iter() here */
    }

    /* similar for ulps_diff */
}

My proposed changes:

  • Change traits to take self
  • f32, f64, Complex remain unchanged since they are all Copy.
  • Add blanket impls for IntoIterator, returning iterators over AbsDiff, etc.
  • Change implementations for [T; N] to &[T; N] (if they are even still necessary, arrays implement IntoIterator)

Let me know what you think and if you'd like a little help implementing it, I didn't want to go change everything and just drop a PR without bringing it up :)

Add an option to control what is #[derive]'d on derived ulps types

Currently deriving FloatEqUlpsEpsilon or FloatEqDebugUlpsDiff creates new types with #[derive(Default, Clone, Copy, PartialEq)] applied to them, but some explicit control over this without having to write the boilerplate out if you need to deviate from this default might be handy.

Derive support for custom types

Traits:

  • FloatUlps (#9)
  • FloatDiff
  • FloatEq
  • FloatEqDebug

It would be useful to be able to derive implementations for simple types that are composed of already comparable types, for example:

#[derive(FloatUlps, FloatDiff, FloatEq, FloatEqDebug)]
struct MyComplex32 {
    re: f32,
    im: f32,
}

Would generate the following:

struct MyComplex32Ulps {
    re: Ulps<f32>,
    im: Ulps<f32>,
}

impl FloatUlps for MyComplex32 {
    type Ulps = MyComplex32Ulps;
}

impl FloatDiff for MyComplex32 {
    type Output = Self;

    fn abs_diff(self, other: Self) -> Self::Output{
        Self::Output {
            re: self.re.abs_diff(other.re),
            im: self.im.abs_diff(other.im),
        }
    }

    fn ulps_diff(self, other: Self) -> Option<Self::UlpsDiff> {
        Some(MyComplex32Ulps {
            re: self.re.ulps_diff(other.re)?,
            im: self.im.ulps_diff(other.im)?,
        })
    }
}

impl FloatEq for MyComplex32 {
    type Epsilon = <f32 as FloatEq>::DiffEpsilon;

    fn eq_abs(&self, other: &Self, max_diff: &Self::DiffEpsilon) -> bool {
        self.re.eq_abs(&other.re, max_diff) && self.im.eq_abs(&other.im, max_diff)
    }

    fn eq_rel(&self, other: &Self, max_diff: &Self::DiffEpsilon) -> bool {
        self.re.eq_rel(&other.re, max_diff) && self.im.eq_rel(&other.im, max_diff)
    }

    fn eq_ulps(&self, other: &Self, max_diff: &Ulps<Self::Epsilon>) -> bool {
        self.re.eq_ulps(&other.re, max_diff) && self.im.eq_ulps(&other.im, max_diff)
    }
}

impl FloatEqDebug for MyComplex32 {
    type DebugEpsilon = Self;

    fn debug_abs_epsilon(&self, other: &Self, max_diff: &Self::DiffEpsilon) -> Self::DebugEpsilon {
        MyComplex32 {
            re: self.re.debug_abs_epsilon(&other.re, max_diff),
            im: self.im.debug_abs_epsilon(&other.im, max_diff),
        }
    }

    fn debug_rel_epsilon(&self, other: &Self, max_diff: &Self::DiffEpsilon) -> Self::DebugEpsilon {
        MyComplex32 {
            re: self.re.debug_rel_epsilon(&other.re, max_diff),
            im: self.im.debug_rel_epsilon(&other.im, max_diff),
        }
    }

    fn debug_ulps_epsilon(
        &self,
        other: &Self,
        max_diff: &Ulps<Self::Epsilon>,
    ) -> Ulps<Self::DebugEpsilon> {
        MyComplex32Ulps {
            re: self.re.debug_ulps_epsilon(&other.re, max_diff),
            im: self.im.debug_ulps_epsilon(&other.im, max_diff),
        }
    }
}

Design: implementing traits over enums (e.g. Result)

The design principles of float_eq make enums difficult to implement the extension traits over, since as it stands all branches of the enum would need to be a floating point type. This obviously makes it difficult to implement for a type like Result, where it is likely that only one of the result type or error type would be comparable. I believe that a way forward would involve thinking through one or more of these options in detail:

  • Implementation only over main result type T (such that the error type is either never equal or using PartialEq).
  • Specialisation over either T, E or both (requires specialisation to be stabilised).
  • Blanket implementation of float_eq comparison traits over non-floating point types (also requires specialisation?).

Therefore, I'm leaving this issue here in case it becomes viable in future, but for now I'm leaving enums (and therefore Result) be.

Comparison of heterogeneous types

Currently FloatEq is built with support for homogeneous types in mind, so for example:

assert_float_eq!([1., 2.], [1., 2.], ulps <= 4);
assert_float_eq!(
    MyType { a: 1., b: 2.},
    MyType { a: 1., b: 2. },
    rel <= 0.001
);

However, explicit support for heterogeneous epsilons might be useful, especially for tuple types. So, for example, you might be able to write comparisons along the lines of:

assert_float_eq!([1., 2.], [1., 2.], ulps <= [4, 8]);
assert_float_eq!((1_f32, 2_f64), (1., 2.), ulps <= (4, 8));
assert_float_eq!(
    MyType { a: 1., b: 2.},
    MyType { a: 1., b: 2. },
    rel <= MyType { a: 0.001, b: 0.005 }
);

Trait impl fallbacks for non-floats

In particular for types like Result or composite types that contain non-floats, it makes sense to introduce a path for non-floats to be considered in floating point equality expressions. Consider how to sensibly do this - diffs should be None and equality should defer to PartialEq.

Change FloatDiff to take its arguments by value

The FloatDiff trait as of the 0.2 release is specified in a non-idiomatic fashion when compared to Rust's standard arithmetic operations, in particular std::ops::Sub, which it is analogous to. This makes it difficult to implement over iterable types. Additionally, since it takes other by reference, it is impossible to implement the following common pattern (see also forward_ref_binop), which can be important for certain optimisations:

impl Sub for MyType
impl Sub for &MyType
impl Sub<&MyTrait> for MyType
impl Sub<&MyTrait> for &MyType

The other consideration is whether FloatDiff ought to be two different traits, one per kind of difference op. Since its primary purpose in this library is to provide debug context information to assert_float_eq and friends, for the moment I'm content to leave it as a single trait, since it compels an implementor to provide all of the required information.

Therefore, the work to be done is that FloatDiff should be changed to the following:

pub trait FloatDiff<Rhs = Self> {
    type AbsDiff;
    type UlpsDiff;

    fn abs_diff(self, other: Rhs) -> Self::AbsDiff;
    fn ulps_diff(self, other: Rhs) -> Self::UlpsDiff;
}

This would necessitate changing the existing implementations (f32, f64, Complex and [T; n]) to take this new form. As part of this change, I'd also like to implement the trait across LHS and RHS references for those types (as Sub is over primitive types).

Deriving extension traits over generic types

Currently the custom #[derive(...)] implementation does not handle derivation over generic types. Deriving the float_eq traits over generics can get pretty messy (see the my_complex_generic test), so I'm leaving this issue here in case anyone fancies trying to tackle this in future.

Debug bounds checks

Add an optional feature (enabled by default) that panics if the epsilon provided to a check for two variably sized items (eg Vec) isn't long enough.

FloatEq impls over slices

There are now FloatEqAll implementations for the *_all checks over slices, but the FloatEq checks are missing. This is because the current trait specification requires that the Epsilon type be something along the lines of [A::Epsilon], which results in errors about the type not being tagged as Sized. Assuming it is possible to implement structured epsilon checks at all, this will need to be solved in terms of defining the impls, or the traits may need to change to accommodate it.

Blanket trait impls for Result

Following the structural model, users should be able to give epsilon variants that match the different enum branches:

assert_float_eq!(Ok(0.1), Ok(0.2), abs <= Ok(0.1)); // true
assert_float_eq!(Ok(0.1), Err(0.2), abs <= Ok(0.1)); // false
assert_float_eq!(Err(0.1), Err(0.2), abs <= Ok(0.1)); // false, epsilon is a different variant
assert_float_eq!(Err(0.1), Err(0.2), abs <= Err(0.1)); // true

// chaining allows multiple variants to be compared
assert_float_eq!(a, b, abs <= Ok(0.1), Err <= (0.1)); 

There is also the question of what to do where enums have branches that aren't floating point values. This would require a solution to #10. It's likely that the common case of Result would be T of some float value and E of a non-float, but it's plausible that the opposite could be true or both could be float comparable. In this case, it might be nice to fall back on PartialEq - which might require specialisation or the implementation of default associated types in traits.

Print float values in asserts in non-scientific format

Rust 1.58 changed how floats output with {:?} are displayed. When they are larger or smaller than a specific threshold, then the output switches to scientific notation. This makes the assert_float_eq! output less helpful, as the non-scientific notation is much clearer for comparing values side by side:

    left: `1.0`,
   right: `1.0000002`,
abs_diff: `0.00000023841858`,

Compare with:

    left: `1.0`,
   right: `1.0000002`,
abs_diff: `2.3841858e-7`,

Unfortunately, there appears to be no way to disable this behaviour with a format specifier. It is plausible that a solution may involve wrapping numeric outputs from float_eq's traits to print in this format. This is a significant amount of work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.