Implement <, >, Ord, PartialOrd on FInt

I have the following code:


#[derive(Clone, Copy, Debug, )]
pub struct FInt {
    data: f32,
}

impl From<f32> for FInt {
    fn from(x: f32) -> FInt {
        FInt { data: x.round() }
    }
}

impl std::cmp::Ord for FInt {
    fn cmp(&self, other: &FInt) -> Ordering {
        cmp(self.data, other.data)
    }
}

impl std::cmp::PartialOrd for FInt {
    fn partial_cmp(&self, other: &FInt) -> Option<Ordering> {
        partial_cmp(self.data, other.data)
    }
}

It's job is to represent a "23-bit Int" via Floating point.

How do I implement <, >, Ord, PartialOrd on the struct above?

=====

Why aren't you just using a i32 ? Because this is representing data for a WebGL buffer, and it turns out, as documented in WebGLRenderingContext.vertexAttribPointer() - Web APIs | MDN that the only supported types are float, short, ushort, byte -- no i32.

Would something like f32::to_bits work?

Although, upon some testing, it seems that a 32 bit float can accurately represent every value from 0..(2 ^ 23), so I think that just using some casting would be okay instead of bit-reinterpretation.

1 Like

Single-precision floating-point format - Wikipedia states 23-bit mantissa, so that's not a problem.

Problem I was running into: apparently Eq is not implemented for f32 in Rust -- as a result, I need to use ParitialEq + PartialOrd, but AVOID Eq and Ord.

That's specified in the IEEE 754 standard (otherwise known as ISO/IEC/IEEE 60559:2011), due to NaNs, which are specified to never compare equal to anything, even themselves. Thus the requirements of Eq cannot be satisfied.

Rust has nothing to do with that constraint, except that Rust conforms to the international floating-point standard.

4 Likes

If you're just representing a 23-bit number, just cast .data as i32 and .cmp() those

This might be the core of my misunderstanding. Are you saying:

  1. Rust Eq requires that NaN == NaN

  2. IEEE 754 requires that NaN != NaN

?

1 Like

Yes. Rust Eq implements the trait std::cmp::Ord, which cannot express the NaN unordered state.

This is to do with total-ordering.

This thread discuss about total-ordering of float. Not sure whether it make sense to implement this for f64 and f32 in Rust.

So to do your FInt, you need to:

  1. Provide constructors that ensure that NaN never enters your type.
  2. Derive PartialEq and PartialOrd.
  3. Implement Eq and Ord, since you can avoid the floating point edge case because of #1.
4 Likes

I absolutely think it does, and would love for someone motivated to pick up this PR and drive the "which form should it take?" conversation to a conclusion:

5 Likes

Last message in that link:

scottmcm commented on Nov 7, 2018 • 
I don't have a direct need for this myself, so I don't think I'll expend the energy needed to resolve it.

LOL :slight_smile:

1 Like

Yeah, I think it should exist, but there are at least three reasonable ways to expose it, and I want to spend my arguing-about-libs time elsewhere

I don't see how it is mathematically possible to resolve this:

  1. Ord requires Eq.

  2. According to Eq in std::cmp - Rust , Eq is required to be Equivalence Relation, which implies that for all values f: f32, we have "f == f", this includes having "NaN == NaN"

  3. Yet, on the other hand, IEEE standard requires that "NaN != NaN".

This seems fundamentally impossible to resolve.

To resolve what?

  • The solution to your problem was given by @cbiffle.
  • The IEEE-754 "Total ordering" thing doesn't really have anything to do with your problem, but it is entirely possible to write a struct TotalOrder(f64) that implements Ord, or for std to provide an inherent method f64::total_order_cmp(self, other: f64) -> Ordering.

What does f64::total_order_cmp(NaN, NaN) return?

According to Wikipedia, the totalOrder predicate will sort them first by their sign bit and whether they are signaling:

negative qNaNs < negative sNaNs < positive sNaNs < positive qNaNs

and within each class, order is determined by comparing sign * payload.


You have to understand, the totalOrder predicate is different from the default ordering scheme of floats. It's not even compatible; for instance, the totalOrder predicate specifies that -0.0 < 0.0, and does not consider different representations of the same value to be equal.

1 Like

I still don't understand this. What are the return values of:

f64::total_order_cmp(-qNaN, -qNaN);
f64::total_order_cmp(-sNaN, -sNaN);
f64::total_order_cmp(+sNaN, +sNaN);
f64::total_order_cmp(+qNaN, +qNaN);

Theyall return Ordering::Equal This is possible because only f64::cmp() promises to fulfill the IEEE-754 specification, but not f64::total_order_cmp().

That is, it compares the payload of the NaNs (the 51 bits of the mantissa that don't include the signaling/quiet bit), and inverts the comparison if they are negative.