[swift-dev] Rationalizing FloatingPoint conformance to Equatable

Jonathan Hull jhull at gbis.com
Fri Oct 27 05:06:15 CDT 2017

> On Oct 26, 2017, at 11:44 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
> On Fri, Oct 27, 2017 at 1:30 AM, Jonathan Hull <jhull at gbis.com <mailto:jhull at gbis.com>> wrote:
> One completely different idea, which I brought up a year or so ago, is to do what we do with pointers around this.  That is you have your fast/unsafe IEEE Floats/Doubles/etc that have a scarier name.  These do not conform to Equatable or Comparable, but have their own version of IEEE equality/comparison. Let’s spell it &== and &< to make it feel different so the users consider the possibility of NaN.  They don’t have any notion of hashability.
> As I wrote in my reply to Greg, IEEE equality and comparison is _the_ best approximation of mathematical equality and comparison suitable for floating-point types. If another one were superior, then floating-point experts would have designated that as the standard.

We definitely have different world views.  I see the handling of NaN as a legacy/compatibility issue due to committee/vendor politics from the 1980’s.  I am pretty sure if they could do it over with modern tech, we would just have isNan() and NaN == NaN… or we might just have optionals instead.

Just to play devil’s advocate, there are actually much better and more accurate representations available using the same number of bits.  The main issue is that there isn’t common hardware support for them.  That is not what I am suggesting here however.

> Swift correctly exposes only one concept of equality for floating-point types. It is and should be IEEE equality. People should be encouraged and not scared to use it. NaN is and should continue to exist as a concept. Yes, IEEE-compliant floating point is hard; the only thing harder than IEEE-compliant floating point is non-IEEE-compliant floating point.

What I am suggesting is identical to IEEE in every way except for NaN.  It is just an IEEE value that has been filtered so that we can guarantee it isn’t NaN. It still uses all the same hardware instructions.  Basically, it semantically converts NaN to nil… and that lets us conform to Equatable/Comparable honestly.  It also still technically adheres to IEEE… it just never comes up because we are careful to filter/handle NaN before the user ever has to deal with it.

If you want/need to use NaN for some reason, you still have the IEEE types. What you can’t do is use them generically for ==.

Can you give me an example of where you would want NaN in a generic context (that might also contain Ints), but an optional Float (which had been filtered not to have NaN) wouldn’t meet your needs?  Remember, this is a generic context, not one that is special casing Floats.

> This thread is meant to discuss how to reconcile this scenario with the semantics of Equatable.

Yes it is… and this is one possible approach to doing it.  We make it so Floats just don’t conform to Equatable, but we make a wrapper type that does conform (and can fully meet the guarantees).

> Then you have your safe/friendly Swift Floating point type(s) which just have no concept of NaN at all (and probably a single notion of zero). You have a failable initializer from the IEEE versions. These types conform to Equatable/Hashable/Comparable.  Care is taken with internal methods so that NaN can’t creep into the type.
> How do we handle math functions which might fail?  We do the same thing we do in the rest of Swift... those functions return an optional.
> When reading in data from the outside world or C code, you would use the IEEE versions and then either convert or do your calculations directly.  They would probably also be used for things like accelerate.  But most code, where the values come from user input or literals, would never even have to touch the IEEE version.
> The advantage here is that you get full speed all the time, even in generic contexts.  You just can’t use the IEEE versions directly in generic contexts. You would have to convert them, which is a one-time cost (or use them non-generically).
> Again, generics and protocol-based numerics are important; that's what Numeric is all about. Any idea that doesn't make this possible is a non-starter.

Why is everything “impossible” or a “non-starter”?  

The wrapper version would adhere to Numeric and would be fully usable in generic contexts.  You can wrap the IEEE Floats, and the NaNs get converted to optionals, where you can apply the generic algorithm.  If your “generic” algorithm was depending on the bit representation of NaN or the fact that it breaks ==, then I would argue you really don’t want a generic algorithm after all… you have a Float specific algorithm.

Also, in terms of speed, we should find something with the semantics we want (that should be the main focus of our discussion)… and then we can figure out how to tweak it so it goes as fast as we need it to once we know what we are aiming for.  Anything else is premature optimization.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-dev/attachments/20171027/1ada8096/attachment.html>

More information about the swift-dev mailing list