13

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.

disruptive
  • 5,687
  • 15
  • 71
  • 135
  • 1
    See also http://stackoverflow.com/q/18686269/155423, and http://floating-point-gui.de/ – Shepmaster Jan 03 '17 at 16:20
  • 1
    @Shepmaster: Given the mention of `round`, I expect the latter, even though it's... probably not what the OP want. – Matthieu M. Jan 03 '17 at 16:24
  • I dont know rust but could you do something like `x-y < 0.002` – ScottishTapWater Jan 03 '17 at 16:24
  • 1
    Imagine that I compare to 2 decimals, is it okay for 1.449999 and 1.450 to be counted as different but 1.450 and 1.459999 to be counted as equal even though the former pair is so much closer to each than the latter? That is, do you really want to compare the decimal representation OR do you want to know whether two numbers are "close enough"? – Matthieu M. Jan 03 '17 at 16:25
  • 1
    Looks like you could take inspiration from the [float_cmp](https://mikedilger.github.io/float-cmp/float_cmp/index.html) crate, or maybe even use it directly. – Frédéric Hamidi Jan 03 '17 at 16:25
  • 1
    @JamesHughes: It's probably what the OP wants, but it's definitely not what they are asking for. – Matthieu M. Jan 03 '17 at 16:28

2 Answers2

14

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 2
    Note: this function may be incorrect for sufficiently large floating pointer numbers/number of decimal combinations; the mantissa of `f64` being only 52 bits after all. – Matthieu M. Jan 03 '17 at 16:29
  • 3
    Oh, and the behaviour around 0 is possibly weird: `approx_equal(0.09, -0.09, 1)` returns `true`, because both numbers round toward 0 in Rust, even though the difference is greater than `0.1`... – Matthieu M. Jan 03 '17 at 16:33
3

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
}
orlp
  • 112,504
  • 36
  • 218
  • 315