disruptive
disruptive

Reputation: 5946

Comparison of two floats in Rust to arbitrary level of precision

How can I do a comparison at an arbitrary level of precision such that I can see that two numbers are the same? In Python, I would use a function like round(), so I am looking for something equivalent in Rust.

For example I have:

let x = 1.45555454;
let y = 1.45556766;

In my case, they are similar up to 2 decimal places. So x and y would become 1.46 for the purposes of comparison. I could format these, but that surely is slow, what is the best Rust method to check equivalence, so:

if x == y { // called when we match to 2 decimal places}

To further elucidate the problem and give some context. This is really for dollars and cents accuracy. So normally in python would use the round() function with all its problems. Yes I am aware of the limitations of floating point representations. There are two functions that compute amounts, I compute in dollars and need to handle the cents part to the nearest penny.

The reason to ask the community is that I suspect that if I roll my own, it could hit performance and it's this aspect - which is I why I'm employing Rust, so here I am. Plus I saw something called round() in the Rust documentation, but it seems to take zero parameters unlike pythons version.

Upvotes: 14

Views: 14501

Answers (2)

user14721330
user14721330

Reputation: 41

This one seems to work pretty well for me.

fn approx_equal (a: f64, b: f64, dp: u8) -> bool {
    let p = 10f64.powi(-(dp as i32));
    (a-b).abs() < p
}

Upvotes: 4

Shepmaster
Shepmaster

Reputation: 431001

From the Python documentation:

Note The behavior of round() for floats can be surprising: for example, round(2.675, 2) gives 2.67 instead of the expected 2.68. This is not a bug: it’s a result of the fact that most decimal fractions can’t be represented exactly as a float.

For more information, check out What Every Programmer Should Know About Floating-Point Arithmetic.


If you don't understand how computers treat floating points, don't use this code. If you know what trouble you are getting yourself into:

fn approx_equal(a: f64, b: f64, decimal_places: u8) -> bool {
    let factor = 10.0f64.powi(decimal_places as i32);
    let a = (a * factor).trunc();
    let b = (b * factor).trunc();
    a == b
}

fn main() {
    assert!( approx_equal(1.234, 1.235, 1));
    assert!( approx_equal(1.234, 1.235, 2));
    assert!(!approx_equal(1.234, 1.235, 3));
}

A non-exhaustive list of things that are known (or likely) to be broken with this code:

  • Sufficiently large floating point numbers and/or number of decimal points
  • Denormalized numbers
  • NaN
  • Infinities
  • Values near zero (approx_equal(0.09, -0.09, 1))

A potential alternative is to use either a fixed-point or arbitrary-precision type, either of which are going to be slower but more logically consistent to the majority of humans.

Upvotes: 15

Related Questions