Avoiding PartialOrd problems by introducing fast finite floating-point types

So let me redefine that: it's a type where INF and NaN can happen. Optimizer should assume that code producing them can exist (by accident), and it's not an undefined behavior.

However, existence of NaN and INF is considered an indication of a bug and treated as malformed input, rather than as legitimate valid input.

The optimizer is still allowed to make optimizations that produce accurate results only for finite values. The optimizer is required to allow code that touches non-finite values to exist (i.e. it's not UB/undef), and is required to make operations on NaN/Inf produce some value, but the value is allowed to be implementation-defined (i.e. garbage like NaN/Inf, 0. 17.12 or any other bit pattern in f32), but the value is not undef and the optimizer is never allowed to spawn nasal demons when NaN or Inf is found.

Right, that's very well. It is a part if IEEE 754 floating-point arithmetic and conditions where NaN appears are indeed very precisely defined.

Very strictly speaking, you could think of what I'm proposing here is to create new floating point type, with its own rules and own definition of NaN, which is not the IEEE 754 float, but a new thing that just happens to look 99% like it so that it can use the existing IEEE 754 hardware for speed.

I fully acknowledge that there are domains where NaN / Inf is valid, useful and necessary. And f32 / f64 are the types to be used there.

However, there are also other domains (in my case image and video codecs) where all the well-defined proper IEEE 754 operations on NaN and Inf are never needed in practice. The nature of the problem is such that these values have no use, and are never intentionally created. In such context a NaN appearing in the program against author's intention is an indication of a bug. For example when I have an array of RGB pixels as floats, then the concept of NaN or Inf does not apply to them. I can't display NaN Red or Inf Blue on screen. If my program happens to produce a non-finite color value, that's a bug in my program, even though the steps that lead to producing that value are well-defined in IEEE 754 spec, it is an indication of my mistake.

The problem is that programs which don't intend to make useful use of NaN still have to pay the cost of it. The code is slower because the compiler is required to preserve very specific NaN handling rules, even when the program by design is incapable of doing anything useful with them.

2 Likes

I understand your usecase but I suppose me and other people here consider that your proposition for fixing this issue is not satisfying.

Now I'm a bit curious: is that really impossible for the cases you're mentioning to create a numeric type (let's call it for example ff32) that really excludes NaN and Inf ? Some operations might naturally make it impossible to produce invalid values (example: sin(x : ff32) naturally produces ff32) and maybe it would not require that many operations where the compiler cannot exclude invalid values (in that case a panic would may still be acceptable). I'm asking because I've always wondered why programming languages do not offer types for positive floats or [0.0, 1.0] floats which preserve a certain number of natural operations

Are you referring to the proposition that NaN/Inf is caught via debug assertions and in release mode it's left up to programmer not to produce these values?

It is possible, and often it is done: via fixed-point arithmetic.

However, floats are usually more convenient to use: f32 has plenty of precision, and the "floating" part is perfect for linear light. OTOH u16.16 while sufficient, requires care and converting to u32, u48.16, etc. Most CPUs also happen to have separate integer and float ALUs, so an algorithm that's part integer part float may use the CPU fully and end up faster than purely-integer algorithm.

A C/C++ programmer would defend nullptr using the same argument, but I think we all agree on Option<T> being more ergonomic than nullptr.

Nan bugs could be prevented the same way that nullptr bugs are prevented, possibly by creating a method called try_div(f64) -> Option<f64>, and making the /-operator panic when the argument is zero, while statically garuanteeing that safe code cannot produce nan values for f64. Just as an example, of course. One could also introduce a new enumeration that also includes infinity instead of using the Option enum.

1 Like

No. UB in Unsafe code is not holding up the contract. "unsafe" is a promise by the developer to the compiler that even though the compiler can't verify it, the developer has promised to uphold the necessary invariants and contracts to ensure no UB is created.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.