Maximum of two floating points numbers

Hello, just started with Rust and trying to learn some of the basics.

Consider this piece of code:

use std::cmp;
fn main() {
    let a: f64 = 1.0;
    let b: f64 = 2.0;
    let c: f64 = cmp::max(a, b);  // << Compiler error
    let d: f64 = a.max(b);  // <<< OK
    println!("c: {}", c);
    println!("d: {}", d);
}

Playpen

I don't understand why a.max(b) is different from cmp::max(a, b).

What is the "proper" translation of C++'s std::max(a, b) to Rust?

Thank you!

1 Like

Well, they are different because they are different functions. The std::cmp::max function requires the Ord trait, which is only implemented for types with a total ordering. Floats are not totally ordered because they have a value called NaN which does not fit in anywhere in the order, so you can't use this method with floats.

The a.max(b) function on the other hand is a function defined directly on the f64 type. You can also call it as f64::max(a, b).

3 Likes

If there is no total ordering good enough for std::cmp::max, then what does f64::max return? And why does a total order matter for comparison of two values?

The documentation says this:

Follows the IEEE-754 2008 semantics for maxNum, except for handling of signaling NaNs. This matches the behavior of libm’s fmin.

Basically, it treats NaN as the smallest possible value. However, f64::min treats NaN as the largest possible value. The order can't be inconsistent in this way with the functions in std::cmp.

1 Like

A total ordering is important because if the order is not total, the two values might be incomparable i.e. neither is smaller than the other, but they also are not equal, so there's no meaningful value to return.

1 Like

So you are saying std::cmp::max is in fact std::max_total_order and f64::max is in fact f64::max_partial_order?

I think it is great that Rust is very verbose about subtleties like that, but it is a bit confusing having two central functions with identical names doing something different..

Well, f64::max is not really a "max partial order". It is is specific to floats, and would not be usable with other partially ordered types.

Well, in this particular case it will not compile if you mix them up, so it's not a big problem in my opinion.

1 Like

Ok, thank you. So you say that C++'s std::max(a, b) translates to a.max(b) in Rust?

Then my question would be how do I translate this C++ code to Rust?

template <typename T>  // intended for float or double
T foo(T a, T b) {
   return std::max(a, b) + T(1);
}

My attempts failed because the cmp::max function requires an Ord trait which doesn't exist for Float, but based on your answer I don't know how to translate the *.max(*) form to a trait.

If you want generics over numeric types in Rust, you're looking for num-traits — Rust math library // Lib.rs

Note, also, that there's two good ways to interpret "maximum" for floating point -- both of which are (or were) in IEEE 754. There's also f64::maximum in nightly now. That difference -- which doesn't exist for Ord types -- is a big part of why there's no Ord::max for floats.

1 Like

Ok, I see .. This seems to work:

fn foo<T: Float>(a: T, b: T) {
  return a.max(b) + ...;
}

Then last question: What does Float::Max return for the pathological cases? Is it documented somewhere?

Actually found it in the source: Float::Max uses f32/64::max and for that the docs say

Follows the IEEE-754 2008 semantics for maxNum, except for handling of signaling NaNs. This matches the behavior of libm’s fmin.

Which I guess is a typo :slight_smile:

Thank you all for your help!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.