Ólavur Nón
Ólavur Nón

Reputation: 395

Get absolute maximum value from f64 array

I have an array, let's say:

let arr: Vec<f64> = vec![1.5, 1.1, -1.6, 0.9, -0.5];

I want the absolute maximum of this array:

let abs_max = arr."some functions";
println!("{}", abs_max); // Gives 1.6 (or -1.6, but not 1.5).

Is there a smart, maybe almost inline, way of doing this? Or do i have to create my own function which iterates through all the values by for loop and compares the values?


I've tried the following suggestion made in this post:

let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()))

And this code gives the following error:

error[E0599]: no method named `abs` found for reference `&&{float}` in the current scope
  --> src\main.rs:51:44
   |
51 |   let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()));
   |                                            ^^^ method not found in `&&{float}`

error[E0599]: no method named `abs` found for reference `&&{float}` in the current scope
  --> src\main.rs:51:62
   |
51 |   let abs_max = arr.iter().max_by(|a, b| a.abs().total_cmp(b.abs()));
   |                                                              ^^^ method not found in `&&{float}`

Upvotes: 2

Views: 1238

Answers (1)

Silvio Mayolo
Silvio Mayolo

Reputation: 70337

In Rust, we usually operate on iterators rather than collections like vectors directly, so all of the juicy "operate on a collection" functions are going to be in std::iter::Iterator.

Now, Iterator has a max function, which would work for integers, but it won't work for floats, because floats are not totally ordered (NaN, as usual, is a problem).

If you had a collection of integers or anything else that implemented the Ord trait, you could write

arr.iter().max()

which would return an Option<T> containing the maximum value, or None if the collection was empty.

However, f64 and the other floating-point types don't implement Ord. Fortunately, the wonderful writers of the Rust documentation for Iterator::max thought of this.

Note that f32/f64 doesn’t implement Ord due to NaN being incomparable. You can work around this by using Iterator::reduce:

assert_eq!(
    [2.4, f32::NAN, 1.3]
        .into_iter()
        .reduce(f32::max)
        .unwrap(),
    2.4
);

So we can apply reduce to f64::max (which is like Ord::max except that it works for the non-total ordering of f64). Adapting a bit to your use case, we get

arr.iter().copied().reduce(f64::max)

Again, this returns an Option<f64> which is empty if the initial collection is empty. You can unwrap if you know the initial collection to be nonempty. Also, if you're never using the array again after this point (i.e. you can pass ownership of it to the iterator), you can use arr.into_iter() rather than arr.iter().copied() to save yourself a copy of each element.

This compares using f64::max, the "usual" ordering of f64. But it sounds like you want a custom ordering, namely the maximum by absolute value. We can use max_by to get this custom ordering.

arr.iter().max_by(|a, b| a.abs().total_cmp(&b.abs()))

Finally, if this is a large project and you don't mind pulling in external dependencies, I recommend OrderedFloat for dealing with all of the awkwardness around NaN and floating point types. It provides an Ord instance for a float-like type which sorts NaN as part of the total ordering, rather than following the (frankly bizarre) IEEE rules for ordering floats. With that library, your maximum becomes

arr.iter().max_by_key(|x| OrderedFloat(x.abs())

Upvotes: 4

Related Questions