assert_eq! with floating point numbers and delta
Asked Answered
B

5

43

Is there a preferred way to do an assert with two floating point numbers and a delta in Rust?

For example...

let a = 3.0;
let b = 2.9999999999;
assert_eq!(a, b, 0.0001); // Imaginary syntax where a ~= b, within 0.0001
Booklover answered 15/6, 2015 at 22:54 Comment(1)
M
18

No. At the moment, you have to check the difference by yourself or use the float-cmp crate.

Also check out the f32 constants.

Myotome answered 16/6, 2015 at 8:44 Comment(0)
K
19

There's also the approx crate which lets you do things like these:

relative_eq!(1.0, 1.0, epsilon = f64::EPSILON);
relative_eq!(1.0, 1.0, max_relative = 1.0);
relative_eq!(1.0, 1.0, epsilon = f64::EPSILON, max_relative = 1.0);
Kieger answered 25/3, 2020 at 21:4 Comment(0)
M
18

No. At the moment, you have to check the difference by yourself or use the float-cmp crate.

Also check out the f32 constants.

Myotome answered 16/6, 2015 at 8:44 Comment(0)
G
11

There's no inbuilt macro for it, but you can create your own.

The following is an implementation of the "absolute error" version described in this article.

macro_rules! assert_delta {
    ($x:expr, $y:expr, $d:expr) => {
        if !($x - $y < $d || $y - $x < $d) { panic!(); }
    },
}

Specifically, the macro assert_delta panics if both the difference between x and y and y and x are greater or equal to d (the "delta" or "epsilon" value, i.e. the tolerance).

This is a bad way to do it because a fixed epsilon, chosen because it "looks small", could actually be way too large when the numbers being compared are very small as well. The comparison would return "true" for numbers that are quite different. And when the numbers are very large, the epsilon could end up being smaller than the smallest rounding error, so that the comparison always returns "false".

Given that the previous implementation breaks in various situations, in general, you should not use it. You may want to implement a more robust macro, e.g. the one that checks for a "relative error".

Grissel answered 16/6, 2015 at 11:58 Comment(2)
I don't think this works. Consider x=2.0, y=1.0, e=0.0001. We expect a panic. x - y is about 1, which is not less than epsilon, so the first part of the or is false and we test the second part. Part 2 of the or is y - x which is about -1, definitely less than epsilon, so the condition returns true. Then, we negate the condition and wind up with the branch being false, not taken, and no panic happens even though the two numbers' absolute difference is greater than 1. Am I missing something?Ludwigg
This is definitely wrong implementation. There should be && instead of ||.Tepper
H
6

There is another complete crate assert_approx_eq solving this pain, better than float-cmp.

use assert_approx_eq::assert_approx_eq;

let a = 3f64;
let b = 4f64;

assert_approx_eq!(a, b); // panics
assert_approx_eq!(a, b, 2f64); //does not panic
assert_approx_eq!(a, b, 1e-3f64); // panics
Hexavalent answered 22/2, 2019 at 3:9 Comment(0)
T
0

If you want to compare not only two floats but two whole structs with approx, you can use the approx-derive crate to automatically derive the necessary AbsDiffEq or RelDiffEq trait to do that. For transparency: I am the author of said crate.

Example from the documentation:

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);
Thompson answered 29/5 at 7:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.