[pitch] Comparison Reform

I certainly don't want to restrain anyone from improving on the floating point status quo. There's surely much to do. I apologize for my not-so-constructive messages. Maybe I'm surprised that this was one of the goals of Swift.

Floats and Strings are efficient ways to test comparison APIs indeed.

Gwendal

···

Le 18 avr. 2017 à 18:58, Joe Groff <jgroff@apple.com> a écrit :

On Apr 18, 2017, at 9:47 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 18 avr. 2017 à 17:40, Joe Groff <jgroff@apple.com> a écrit :

On Apr 18, 2017, at 1:34 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 18 avr. 2017 à 06:45, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

On Apr 17, 2017, at 9:40 PM, Chris Lattner <clattner@nondot.org> wrote:

On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be vended as part of XCTest, in order to make sure that `XCTAssertEqual(resultOfComputation, Double.nan)` always fails.

Unit tests strike me as an example of where you really *don't* want level 1 comparison semantics. If I'm testing the output of an FP operation, I want to be able to test that it produces nan when I expect it to, or that it produces the right zero.

I find it very concerning that == will have different results based on concrete vs generic type parameters. This can only lead to significant confusion down the road. I’m highly concerned about situations where taking a concrete algorithm and generalizing it (with generics) will change its behavior.

In this case, I think we're making == do exactly what you want it to do based on context. If you're working concretely with floats, then you get floating-point behavior like you'd expect. If you're working with generically Equatable/Comparable things, then you're expecting the abstract guarantees of interchangeability and total ordering that implies, and you don't want to have to think about the edge cases of weird types that violate those constraints. I also doubt that this will cause problems in practice.

"then you're expecting the abstract guarantees of interchangeability and total ordering that implies"

Joe, please: I'm very glad that you are expert in so many subject - I'd love to have your job - but please keep track of average joes that have to scratch their heads whenever they have to deal with nans and infinites and subnormals and all those weird floating beasts. They already scratch their heads with the idiosyncrasies of Swift protocols.

Please keep equality simple.

I would argue that keeping equality simple is exactly what this proposal achieves, since you *don't* need to worry about nans or positive/negative zero or other floating-point strangeness every place you use == abstractly. You have to opt in to the idiosyncratic behavior by working concretely with floats.

Cool, a brand new Swift chapter added to "What Every Computer Scientist Should Know About Floating-Point Arithmetic" :-)

IMO, it will be a victory if the Swift edition can be titled "What Every Computer Scientist *Who Uses Floats* Should Know About Them", and everyone else who doesn't think about floating-point every day can sleep easy at night without Kahan's ghost hiding under their bed.

-Joe

Ok, if you can name and shame the inventor of the CGFloat type ;)

···

Sent from my iPhone

On 18 Apr 2017, at 17:58, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 18, 2017, at 9:47 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 18 avr. 2017 à 17:40, Joe Groff <jgroff@apple.com> a écrit :

On Apr 18, 2017, at 1:34 AM, Gwendal Roué <gwendal.roue@gmail.com> wrote:

Le 18 avr. 2017 à 06:45, Joe Groff via swift-evolution <swift-evolution@swift.org> a écrit :

On Apr 17, 2017, at 9:40 PM, Chris Lattner <clattner@nondot.org> wrote:

On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be vended as part of XCTest, in order to make sure that `XCTAssertEqual(resultOfComputation, Double.nan)` always fails.

Unit tests strike me as an example of where you really *don't* want level 1 comparison semantics. If I'm testing the output of an FP operation, I want to be able to test that it produces nan when I expect it to, or that it produces the right zero.

I find it very concerning that == will have different results based on concrete vs generic type parameters. This can only lead to significant confusion down the road. I’m highly concerned about situations where taking a concrete algorithm and generalizing it (with generics) will change its behavior.

In this case, I think we're making == do exactly what you want it to do based on context. If you're working concretely with floats, then you get floating-point behavior like you'd expect. If you're working with generically Equatable/Comparable things, then you're expecting the abstract guarantees of interchangeability and total ordering that implies, and you don't want to have to think about the edge cases of weird types that violate those constraints. I also doubt that this will cause problems in practice.

"then you're expecting the abstract guarantees of interchangeability and total ordering that implies"

Joe, please: I'm very glad that you are expert in so many subject - I'd love to have your job - but please keep track of average joes that have to scratch their heads whenever they have to deal with nans and infinites and subnormals and all those weird floating beasts. They already scratch their heads with the idiosyncrasies of Swift protocols.

Please keep equality simple.

I would argue that keeping equality simple is exactly what this proposal achieves, since you *don't* need to worry about nans or positive/negative zero or other floating-point strangeness every place you use == abstractly. You have to opt in to the idiosyncratic behavior by working concretely with floats.

Cool, a brand new Swift chapter added to "What Every Computer Scientist Should Know About Floating-Point Arithmetic" :-)

IMO, it will be a victory if the Swift edition can be titled "What Every Computer Scientist *Who Uses Floats* Should Know About Them", and everyone else who doesn't think about floating-point every day can sleep easy at night without Kahan's ghost hiding under their bed.

-Joe
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

>
>>
>>
>>
>>
>>
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

>> This is a bump in the rug – push it down in one place, it pops up in
>> another. I feel like this proposal at least moves the bump to where
fewer
>> people will trip over it. I think it highly likely that the
intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who
hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the
rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find
it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

> My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

No, in my alternative proposal:

   let nan = 0.0 / 0.0
   print(nan == nan)     // trap
   print([nan] == [nan]) // trap
   print(nan &== nan) // false
   print([nan] &== [nan]) // false
···

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < > > swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < > >> swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < > >> swift-evolution@swift.org> wrote:
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < > >> swift-evolution@swift.org> wrote:

I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

--
-Dave

>
>>
>>
>>
>>
>>
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal
as written promises FP-aware versions of these functions. That is to say, I
would expect the standard library to supply an alternative implementation
of equality for Array<T where T : FloatingPoint>.

This is a bump in the rug – push it down in one place, it pops up in

>> another. I feel like this proposal at least moves the bump to where
fewer
>> people will trip over it. I think it highly likely that the
intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who
hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the
rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find
it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically migrated
to an alternative set of comparison operators `&==`, `&<`, etc. that would
work exactly as `==`, `<`, etc. do today. I would quibble with the notion
that all such generic algorithms currently "just work," but the result is
that they would behave exactly as they do today and therefore would at
least be no more broken.

Standard library changes to `sort` and other functions will make them "just
work" with no distinguishable difference to the end user as compared to
this proposal here. It would be an improvement over how the algorithms work
today with NaN.

The major difference to the end user between what I propose and this
proposal here will surface when _new_ code is written that uses `==` in the
generic context, when working with types whose values may compare
unordered. Since I propose `<=>` to return a value of type `Comparison?`,
using the revised operator `==` is an assertion that the result of
comparison is not unordered. A user is welcome to use `&==` or a custom
predicate if that is not their intention.

My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I
left unanswered?

···

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < > > swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < > >> swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < > >> swift-evolution@swift.org> wrote:
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < > >> swift-evolution@swift.org> wrote:

I'll refer you to Steve Canon's answer on StackOverflow: floating point - What is the rationale for all comparisons returning false for IEEE754 NaN values? - Stack Overflow

I get what he is saying about not being able to suddenly change C’s definition to the opposite boolean value (even if they would have designed it differently for modern systems). We aren’t C though. And nil is a different name than NaN. This gives us a way to make a change without breaking conformance.

I really like this idea, but it’s not true to say this doesn’t break IEEE-compliance if what we expose as NaN no longer compares not equal to itself.

I would argue that we aren’t actually exposing NaN, so much as we are exposing something which is different, but stays in sync with it. NaN is still there in the format under the surface, but you wouldn’t be able to access it directly (only indirectly).

The problem is that the IEEE spec requires that certain operations return NaN. Either nil is NaN in this model, and we’re breaking the spec for equality, or nil is different from NaN in this model, and we’re breaking the spec for when NaN should be produced.

It is a bit of a magic trick. Whenever the value is NaN, we only let you access ‘.none’ instead of the value. So, if you were able to directly compare NaN with NaN, it would be false (or unordered), but you can’t get ahold of them to do that comparison.

I don’t find this argument compelling. Again, if it doesn’t act like NaN, it isn’t following spec. It doesn’t matter that it has the same bit representation.

The important thing to me is binary compatibility. Algorithms need to be rewritten a bit anyway when moving them into swift, so that isn’t a big deal to write it for nil instead of NaN… especially when the compiler helps you due to the optional. But data, which might contain NaN, needs to be read/written in an interchangeable format. That is why I suggest having Optional do the lifting to match NaN. Anytime it crosses a boundary which strips the wrapper, it will just work (as long as we import as Double?).

I think this would be a cool idea were it not that so many people are against breaking the spec. This trick did work well for UnsafePointer, but the circumstances were different.

Anyway, I would be partially in favor of this idea, but I don’t think there’s any way the community as a whole is going to be on-board.

Bottom line is, whatever it's designed for, it's here to stay. I'm *not* on any IEEE committee; I'm not qualified to design an alternative universe of floating point types; and the Swift evolution mailing list (or even Swift itself) is not the place to design one. I and others rely on Swift floating point types to be IEEE-complaint, so that's where we start the discussion here about Comparable.

It *is* IEEE compliant (or at least the same amount we currently are), what is different is the way we interface with that in code. You can think of Swift Double as a simple (runtime-cost-free) wrapper around the compiler’s double that limits how you can touch it (in order to provide additional safety guarantees). Sounds very swifty to me… we do similar things with other C constructs using various wrappers. We aren’t getting rid of NaN behind the scenes, just packaging it’s use in a way which aligns with Swift as a whole… What changes is the programer’s mental model. The bits are exactly the same.

If the naming is what bothers you, we could even just create a “new” Swift type that is this wrapper, and then discourage the use of Double outside of places where NaN is needed. I feel like NaN is actually needed in relatively few places though…

The programmer (mental) model would be that Swift Double just doesn’t have NaN, and anywhere where you would normally return NaN, you return nil instead.

Look, you can already use Double? today. There is no barrier to trying it out for yourself. However, `Double?.none` doesn't mean the same thing as `Double.nan`. The former indicates that _there is no value_; the latter indicates that there _is_ a value, it's just _not a number_. Suppose I parse a list of numbers into an array. If I ask for [Double].last and get nil, it's telling me that _there are no elements in the array_. If I get .nan, it's telling me that there *is* a value, it just wasn't a number. In the former case, I'd do nothing. In the latter case, I might prompt the user: this isn't a number!

However, the property of using NaN’s bits to represent nil let’s us inter-op seamlessly with C and ObjC (or any other language’s) code. They just treat it as a double with NaN as normal (including NaN != NaN) and we interface with it as ‘Double?'

I'm going to sound like a broken record, now. Whether floating point types in Swift conform to IEEE standards is _not_ up for discussion, afaict; that's simply a given. Now, around that constraint, we're trying to design a revised Comparable protocol. Code written today for floating point work expects NaN != NaN. That is just something that is and will forever be. We break source compatibility if we change that.

As I said above, they *do* conform to those standards… they just don’t expose the full capabilities directly to the end programmer. The capabilities are still there, you just have to be more explicit about their use.

I could be wrong, but I believe that in the current version of Swift, the result of doing comparisons with NaN is actually undefined at the moment… so it isn’t breaking source compatibility with the defined language. Just with code which is using implementation artifacts…

I think you’re incorrect. It says in the docs for NaN that it always compares false with itself.

That’s too bad. I was looking in the language guide. I guess it would be a breaking change to change Float/Double. We could still use the idea though, we would just have to add the wrapper as a new type with a different name.

I’d be a lot more in favor of this as compared to the Optional suggestion above, but I’m worried about the API surface impact. It also does not solve our `Equatable` and `Comparable` issues…

I guess we could just say `PotentiallyUnknown<Double>` doesn’t conform to either, and you need to unwrap if you’d like to compare. We could also add optional-returning comparison functions without creating a formal protocol. I think this would all be pretty reasonable.

I would be in favor of renaming Float/Double to something like CFloat/CDouble, and then reusing the names for the wrappers. Not sure if I could convince the powers that be of that though…

We could also write `typealias CDouble = PartiallyUnknown<Double>` so it’s less painful to work with. I’d be very happy if `Double` trapped.

I am not sure how much code actually uses NaN in this way in the real world.

C code might require some simple changes to work in Swift, but as you argued passionately before, that is already to be expected. We shouldn’t have the expectation of copy/pasting C code without thinking through the differences in &+, etc...

This did not always work correctly, if I recall, but it does now and it's an intentional part of the design. However, NaN must compare not equal to every NaN. These could not be more different properties. It seems quite absurd on its face that we might want NaN to compare equal to a value of type `Optional<UIViewController>`.

Is there an algorithm that requires NaN != NaN that couldn’t be reasonably rewritten to handle nil/optionals instead?

I don't need an algorithm to show you the problem. See this expression: `0 * Double.infinity == .infinity * 0` correctly evaluates to false. Zero times infinity is simply not equal to infinity times zero. You're suggesting a design where the result would be true, and that simply won't fly, because it's just not true.

IEEE 754 says that the result of any comparison with NaN should be *undefined*. C’s implementation is the one who brings us this idea that NaN != NaN. The *correct* evaluation according to 754 would be ‘undefined’. We aren’t breaking 754, just breaking away from a C convention… in a way which is very much in line with how Swift breaks away from other C conventions.

I don’t think this is true. I got excited for a minute and looked it up. Where do you see that comparison behavior is undefined?

Oh, sorry, I didn’t mean that the behavior is undefined… I used the wrong word (freudian slip). I meant to say that the correct evaluation is *unordered*. Four possibilities are defined in the spec: GreaterThan, LessThan, Equal, and Unordered. NaN is always unordered when compared with anything.
From the spec:

For every supported arithmetic format, it shall be possible to compare one floating-point datum to another in that format (see 5.6.1). Additionally, floating-point data represented in different formats shall be comparable as long as the operands’ formats have the same radix.
Four mutually exclusive relations are possible: less than, equal, greater than, and unordered. The last case arises when at least one operand is NaN. Every NaN shall compare unordered with everything, including itself. Comparisons shall ignore the sign of zero (so +0 = −0). Infinite operands of the same sign shall compare equal.

And as I read further, It does appear I was wrong about NaN != NaN (when doing a partial order). I had somehow missed it in my first read-through In the 2008 version of the spec, it goes on to say:

Languages define how the result of a comparison shall be delivered, in one of two ways: either as a relation identifying one of the four relations listed above, or as a true-false response to a predicate that names the specific comparison desired.

Which means that we should have 4 functions: ==, <, >, and isUnordered… where isUnordered is the only one which returns true for NaN

That sounds a lot more like what I expected, cool.

One loophole is that it gives alternate names for those functions which we could use for the official behavior (and still use < and == for our strict total order):
• compareQuietEqual
• compareQuietGreater
• compareQuietLess
• compareQuietUnordered

Are you suggesting that `<` and friends trap? Is this still in the type-system aware NaN world, or is this an entirely different option you’re exploring?

Also, we should all read section 5.10, as it seems to give a canonical answer of how to do a total order for 754:

totalOrder(x, y) imposes a total ordering on canonical members of the format of x and y:

a) If x < y, totalOrder(x, y) is true.

b) If x > y, totalOrder(x, y) is false.

c) Ifx=y:

1) totalOrder(−0, +0) is true.

2) totalOrder(+0, −0) is false.

3) If x and y represent the same floating-point datum:

i) If x and y have negative sign,
totalOrder(x, y) is true if and only if the exponent of x ≥ the exponent of y

ii) otherwise
totalOrder(x, y) is true if and only if the exponent of x ≤ the exponent of y.

d) If x and y are unordered numerically because x or y is NaN:

1) totalOrder(−NaN, y) is true where −NaN represents a NaN with negative sign bit and y is a

floating-point number.

2) totalOrder(x, +NaN) is true where +NaN represents a NaN with positive sign bit and x is a floating-point number.

3) If x and y are both NaNs, then totalOrder reflects a total ordering based on:

i) negative sign orders below positive sign

Oh god, I didn’t realize that NaN could be signed…

ii) signaling orders below quiet for +NaN, reverse for −NaN

iii) lesser payload, when regarded as an integer, orders below greater payload for +NaN, reverse for −NaN.

Neither signaling NaNs nor quiet NaNs signal an exception. For canonical x and y, totalOrder(x, y) and totalOrder( y, x) are both true if x and y are bitwise identical.

Here we see NaN == NaN when doing a total order (as long as the payloads are the same).

Thanks,
Jon

Cheers,
Jaden Geller

···

On Apr 25, 2017, at 11:50 PM, Jonathan Hull <jhull@gbis.com> wrote:

On Apr 25, 2017, at 9:34 PM, Jaden Geller <jaden.geller@gmail.com <mailto:jaden.geller@gmail.com>> wrote:

On Apr 25, 2017, at 8:28 PM, Jonathan Hull via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 25, 2017, at 7:17 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

Maybe we should make Float/Double conform to "IEEE754Comparable"/"IEEE754Equatable" instead of "Comparable"/"Equatable". Then it's all a moot point since floating point types wouldn't end up in the same generic functions as other comparable types.

(Not sure if I'm serious)

- Dave Sweeris

···

On Apr 22, 2017, at 14:53, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>
>>
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different starting
>> point. If you start with a concrete algorithm on Int, then generalize it to
>> all Equatable types, then your algorithm will have unexpected behavior for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal as written promises FP-aware versions of these functions. That is to say, I would expect the standard library to supply an alternative implementation of equality for Array<T where T : FloatingPoint>.

>> This is a bump in the rug – push it down in one place, it pops up in
>> another. I feel like this proposal at least moves the bump to where fewer
>> people will trip over it. I think it highly likely that the intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically migrated to an alternative set of comparison operators `&==`, `&<`, etc. that would work exactly as `==`, `<`, etc. do today. I would quibble with the notion that all such generic algorithms currently "just work," but the result is that they would behave exactly as they do today and therefore would at least be no more broken.

Standard library changes to `sort` and other functions will make them "just work" with no distinguishable difference to the end user as compared to this proposal here. It would be an improvement over how the algorithms work today with NaN.

The major difference to the end user between what I propose and this proposal here will surface when _new_ code is written that uses `==` in the generic context, when working with types whose values may compare unordered. Since I propose `<=>` to return a value of type `Comparison?`, using the revised operator `==` is an assertion that the result of comparison is not unordered. A user is welcome to use `&==` or a custom predicate if that is not their intention.

> My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I left unanswered?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

>
>>
>>
>>
>>
>>
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

>> This is a bump in the rug – push it down in one place, it pops up in
>> another. I feel like this proposal at least moves the bump to where
fewer
>> people will trip over it. I think it highly likely that the
intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who
hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the
rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find
it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

> My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

No, in my alternative proposal:

   let nan = 0.0 / 0.0
   print(nan == nan)     // trap
   print([nan] == [nan]) // trap
   print(nan &== nan) // false
   print([nan] &== [nan]) // false

Oh, that's an interesting approach. Now you are asking people to
translate the == in numeric code. I guess I'd want to hear what Steve
Canon has to say about that.

It still begs all the questions I've asked above, though. Should I
repeat them?

···

on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

--
-Dave

--
-Dave

Maybe we should make Float/Double conform to
"IEEE754Comparable"/"IEEE754Equatable" instead of
"Comparable"/"Equatable". Then it's all a moot point since floating
point types wouldn't end up in the same generic functions as other
comparable types.

(Not sure if I'm serious)

Do you want to ban

  Dictionary<Float,String>

? That would be one consequence.

···

on Sat Apr 22 2017, David Sweeris <davesweeris-AT-mac.com> wrote:

- Dave Sweeris

On Apr 22, 2017, at 14:53, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >>> > swift-evolution@swift.org> wrote:
>
>>
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >>> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >>> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >>> >> swift-evolution@swift.org> wrote:
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different starting
>> point. If you start with a concrete algorithm on Int, then generalize it to
>> all Equatable types, then your algorithm will have unexpected behavior for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the
proposal as written promises FP-aware versions of these
functions. That is to say, I would expect the standard library to
supply an alternative implementation of equality for Array<T where T
: FloatingPoint>.

>> This is a bump in the rug – push it down in one place, it pops up in
>> another. I feel like this proposal at least moves the bump to where fewer
>> people will trip over it. I think it highly likely that the intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically
migrated to an alternative set of comparison operators `&==`, `&<`,
etc. that would work exactly as `==`, `<`, etc. do today. I would
quibble with the notion that all such generic algorithms currently
"just work," but the result is that they would behave exactly as
they do today and therefore would at least be no more broken.

Standard library changes to `sort` and other functions will make
them "just work" with no distinguishable difference to the end user
as compared to this proposal here. It would be an improvement over
how the algorithms work today with NaN.

The major difference to the end user between what I propose and this
proposal here will surface when _new_ code is written that uses `==`
in the generic context, when working with types whose values may
compare unordered. Since I propose `<=>` to return a value of type
`Comparison?`, using the revised operator `==` is an assertion that
the result of comparison is not unordered. A user is welcome to use
`&==` or a custom predicate if that is not their intention.

> My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I left unanswered?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
-Dave

>
>>
>>
>>
>>
>>
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal
as written promises FP-aware versions of these functions.

Where do you see that promise? If we said or even implied that, I
didn't review the text carefully enough.

That is to say, I would expect the standard library to supply an
alternative implementation of equality for Array<T where T :
>.

And also for Dictionary? What do you expect to happen when Double is
used as a dictionary key and it happens to be NaN?

>> This is a bump in the rug – push it down in one place, it pops up
>> in another. I feel like this proposal at least moves the bump to
>> where
fewer
>> people will trip over it. I think it highly likely that the
intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who
hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the
rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find
it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically migrated
to an alternative set of comparison operators `&==`, `&<`, etc. that would
work exactly as `==`, `<`, etc. do today.

Meaning, for floating point NaN &== NaN is false, and if you want to
write numeric code that accounts for NaN, you use &==.

OK, so... Is &== a protocol requirement, or a protocol extension, or
neither? If so, to which protocol is it attached?

I would quibble with the notion that all such generic algorithms
currently "just work,"

I never claimed they do! They don't, because Equatable.== for floating
point is not an equivalence relation. That's part of what we aim to
fix.

You are proposing to fix that same problem a different way, one that leaves
NaNs a bit out-in-the-cold (not necessarily bad), but also explicitly
modifies generic algorithms so they continue to silently produce
unspecified results (bad!)

but the result is that they would behave exactly as they do today and
therefore would at least be no more broken.

If that's all we acheive, we should do nothing.

Standard library changes to `sort` and other functions will make them
"just work" with no distinguishable difference to the end user as
compared to this proposal here.

I'm sorry, I don't know what "this proposal here" means. Is that yours
or the one Ben and I offered? It's certainly different from the results
of our proposal.

The big problem with our proposal, AFAICT, is that

    floatsIncludingNaNs.sort()

works but

    floatsIncludingNaNs.sort(>)

does not. That is a real problem, but it *is* a difference from the
current behavior, where neither one works.

It would be an improvement over how the algorithms work today with
NaN.

The major difference to the end user between what I propose and this
proposal here will surface when _new_ code is written that uses `==` in the
generic context, when working with types whose values may compare
unordered. Since I propose `<=>` to return a value of type `Comparison?`,
using the revised operator `==` is an assertion that the result of
comparison is not unordered. A user is welcome to use `&==` or a custom
predicate if that is not their intention.

The problem with this is that there's still no simple way to get an
equivalence relation or a total order over all Doubles, including NaNs.

Now, I'm totally willing to have the discussion about how NaNs have no
business being used as dictionary keys, or sort keys, or searched for,
or any of the other things we do with day-to-day values. That's not
something I really have an opinion on, yet. I am, however, concerned
that ordinary valid computations can lead to NaN and that allowing the
appearance of a NaN to turn into a trap much later in the program, where
it is finally compared with something, is not a behavior that would work
for ordinary users.

My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I
left unanswered?

Again:

  Why is it the right behavior, for our audience, to trap when Equatable
  comparison happens to encounter NaN?

···

on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:

--
-Dave

Maybe we should make Float/Double conform to "IEEE754Comparable"/"
IEEE754Equatable" instead of "Comparable"/"Equatable". Then it's all a
moot point since floating point types wouldn't end up in the same generic
functions as other comparable types.

(Not sure if I'm serious)

Dave, this is similar in principle to having two protocols,
PartialComparable and Comparable. See the original proposal text about why
this alternative is not being considered. In brief, allowing floating point
types to be used in the same generic functions as other types is considered
to be essential.

- Dave Sweeris

···

On Sat, Apr 22, 2017 at 5:39 PM, David Sweeris <davesweeris@mac.com> wrote:

On Apr 22, 2017, at 14:53, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> > wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>
>>
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that
`XCTAssertEqual(resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal
as written promises FP-aware versions of these functions. That is to say, I
would expect the standard library to supply an alternative implementation
of equality for Array<T where T : FloatingPoint>.

>> This is a bump in the rug – push it down in one place, it pops up in

>> another. I feel like this proposal at least moves the bump to where
fewer
>> people will trip over it. I think it highly likely that the
intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who
hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the
rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug
might
> overlie the bump, but the bump will always be there and people will
find it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically migrated
to an alternative set of comparison operators `&==`, `&<`, etc. that would
work exactly as `==`, `<`, etc. do today. I would quibble with the notion
that all such generic algorithms currently "just work," but the result is
that they would behave exactly as they do today and therefore would at
least be no more broken.

Standard library changes to `sort` and other functions will make them
"just work" with no distinguishable difference to the end user as compared
to this proposal here. It would be an improvement over how the algorithms
work today with NaN.

The major difference to the end user between what I propose and this
proposal here will surface when _new_ code is written that uses `==` in the
generic context, when working with types whose values may compare
unordered. Since I propose `<=>` to return a value of type `Comparison?`,
using the revised operator `==` is an assertion that the result of
comparison is not unordered. A user is welcome to use `&==` or a custom
predicate if that is not their intention.

> My purpose in exploring an alternative design is to see if it would be

> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I
left unanswered?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

>
>>
>>
>> >
>> >>
>> >>
>> >>
>> >>
>> >>
>> >>
>> >> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> >> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
>> resultOfComputation,
>> >> Double.nan)` always fails.
>> >>
>> >>
>> >> Unit tests strike me as an example of where you really *don't* want
>> level
>> >> 1 comparison semantics. If I'm testing the output of an FP
operation, I
>> >> want to be able to test that it produces nan when I expect it to, or
>> that
>> >> it produces the right zero.
>> >>
>> >>
>> >> I find it very concerning that == will have different results based
on
>> >> concrete vs generic type parameters. This can only lead to
significant
>> >> confusion down the road. I’m highly concerned about situations where
>> >> taking a concrete algorithm and generalizing it (with generics) will
>> change
>> >> its behavior.
>> >>
>> >>
>> >> It is already the case that you can start with a concrete algorithm,
>> >> generalize it, and get confusing results – just with a different
>> starting
>> >> point. If you start with a concrete algorithm on Int, then generalize
>> it to
>> >> all Equatable types, then your algorithm will have unexpected
behavior
>> for
>> >> floats, because these standard library types fail to follow the rules
>> >> explicitly laid out for conforming to Equatable.
>> >>
>> >> This is bad. Developers need to be able to rely on those rules. The
>> >> standard library certainly does:
>> >>
>> >> let a: [Double] = [(0/0)]
>> >> var b = a
>> >>
>> >> // true, because fast path buffer pointer comparison:
>> >> a == b
>> >>
>> >> b.reserveCapacity(10) // force a reallocation
>> >>
>> >> // now false, because memberwise comparison and nan != nan,
>> >> // violating the reflexivity requirement of Equatable:
>> >> a == b
>> >>
>> >>
>> >> Maybe we could go through and special-case all the places in the
>> standard
>> >> library that rely on this, accounting for the floating point behavior
>> >> (possibly reducing performance as a result). But we shouldn't expect
>> users
>> >> to.
>> >>
>> >
>> > I was not thinking about the issue illustrated above, but this is
>> > definitely problematic to me.
>> >
>> > To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
>> > to compare unequal with itself, yes?
>>
>> Nope.
>>
>> As you know, equality of arrays is implemented generically and based on
>> the equatable conformance of their elements. Therefore, two arrays of
>> equatable elements are equal iff the conforming implementation of
>> Equatable's == is true for all elements.
>>
>> > It is very clear that here we are working with a concrete FP type and
>> > not in a generic context, and thus all IEEE FP behavior should apply.
>>
>> I suppose that's one interpretation, but it's not the right one.
>>
>> If this were C++, it would be different, because of the way template
>> instantiation works: in a generic context like the == of Array, the
>> compiler would look up the syntactically-available == for the elements
>> and use that. But Swift is not like that; static lookup is done at the
>> point where Array's == is compiled, and it only finds the == that's
>> supplied by the Element's Equatable conformance.
>>
>> This may sound like an argument based on implementation details of the
>> language, and to some extent it is. But that is also the fundamental
>> nature of the Swift language (and one for which we get many benefits),
>> and it is hopeless to paper over it. For example, I can claim that all
>> doubles are equal to one another:
>>
>> 9> func == (lhs: Double, rhs: Double) -> Bool { return true }
>> 10> 4.0 == 1.0
>> $R2: Bool = true
>> 11> [4.0] == [1.0] // so the arrays should be equal too!
>> $R3: Bool = false
>>
>> Another way to look at this is that Array is not a numeric vector, and
>> won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
>> would be wrong for you to expect it to reflect the numeric properties of
>> its elements.
>>
>> >> This is a bump in the rug – push it down in one place, it pops up in
>> >> another. I feel like this proposal at least moves the bump to where
>> fewer
>> >> people will trip over it. I think it highly likely that the
>> intersection of
>> >> developers who understand enough about floating point to write truly
>> >> correct concrete code, but won’t know about or discover the
documented
>> >> difference in generic code, is far smaller than the set of people who
>> hit
>> >> problems with the existing behavior.
>> >>
>> >
>> > So, to extend this analogy, I'd rather say that the bump is not in the
>> rug
>> > [Comparable] but rather in a section of the floor [FP NaN]. The rug
might
>> > overlie the bump, but the bump will always be there and people will
find
>> it
>> > as they walk even if they don't immediately see it.
>>
>> Correct.
>>
>> > If we don't want people to trip over the bump while walking on the
>> > rug, one very good alternative, IMHO, is to shape the rug so that it
>> > doesn't cover the bump.
>>
>> At what cost?
>>
>> More specifically: why is it the right behavior, for our audience, to
>> trap when Equatable comparison happens to encounter NaN? Will this not
>> simply "crash" programs in the field that otherwise would have "just
>> worked?"
>>
>> > My purpose in exploring an alternative design is to see if it would be
>> > feasible for non-FP-aware comparison operators to refuse to compare
NaN,
>> > rather than giving different answers depending on context.
>>
>> So... to be clear, this is still different behavior based on context.
>> Is this not just as confusing a result?
>>
>> let nan = 0.0 / 0.0
>> print(nan == nan) // false
>> print([nan] == [nan]) // trap
>>
>
> No, in my alternative proposal:
>
> ```
> let nan = 0.0 / 0.0
> print(nan == nan) // trap
> print([nan] == [nan]) // trap
> print(nan &== nan) // false
> print([nan] &== [nan]) // false
> ```

Oh, that's an interesting approach. Now you are asking people to
translate the == in numeric code. I guess I'd want to hear what Steve
Canon has to say about that.

It still begs all the questions I've asked above, though. Should I
repeat them?

Please do. I thought I answered all of them, so if I'm not addressing some
central question I'd like to know :)

···

On Sat, Apr 22, 2017 at 5:51 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> > wrote:
>> on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
>> > On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < > >> > swift-evolution@swift.org> wrote:
>> >> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < > >> >> swift-evolution@swift.org> wrote:
>> >> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < > >> >> swift-evolution@swift.org> wrote:
>> >> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < > >> >> swift-evolution@swift.org> wrote:

>> > I now strongly believe that this may make for a design simultaneously
>> > _less_ complex *and* _more_ comprehensive (as measured by the
>> > flatness-of-rug metric).
>>
>> I'm certainly willing to discuss it, but so far it doesn't seem like
>> you've been willing to answer the central questions above.
>>
>> --
>> -Dave
>>

--
-Dave

>
>>
>>
>> >
>> >>
>> >>
>> >>
>> >>
>> >>
>> >>
>> >> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> >> vended as part of XCTest, in order to make sure that `XCTAssertEqual(
>> resultOfComputation,
>> >> Double.nan)` always fails.
>> >>
>> >>
>> >> Unit tests strike me as an example of where you really *don't* want
>> level
>> >> 1 comparison semantics. If I'm testing the output of an FP
operation, I
>> >> want to be able to test that it produces nan when I expect it to, or
>> that
>> >> it produces the right zero.
>> >>
>> >>
>> >> I find it very concerning that == will have different results based
on
>> >> concrete vs generic type parameters. This can only lead to
significant
>> >> confusion down the road. I’m highly concerned about situations where
>> >> taking a concrete algorithm and generalizing it (with generics) will
>> change
>> >> its behavior.
>> >>
>> >>
>> >> It is already the case that you can start with a concrete algorithm,
>> >> generalize it, and get confusing results – just with a different
>> starting
>> >> point. If you start with a concrete algorithm on Int, then generalize
>> it to
>> >> all Equatable types, then your algorithm will have unexpected
behavior
>> for
>> >> floats, because these standard library types fail to follow the rules
>> >> explicitly laid out for conforming to Equatable.
>> >>
>> >> This is bad. Developers need to be able to rely on those rules. The
>> >> standard library certainly does:
>> >>
>> >> let a: [Double] = [(0/0)]
>> >> var b = a
>> >>
>> >> // true, because fast path buffer pointer comparison:
>> >> a == b
>> >>
>> >> b.reserveCapacity(10) // force a reallocation
>> >>
>> >> // now false, because memberwise comparison and nan != nan,
>> >> // violating the reflexivity requirement of Equatable:
>> >> a == b
>> >>
>> >>
>> >> Maybe we could go through and special-case all the places in the
>> standard
>> >> library that rely on this, accounting for the floating point behavior
>> >> (possibly reducing performance as a result). But we shouldn't expect
>> users
>> >> to.
>> >>
>> >
>> > I was not thinking about the issue illustrated above, but this is
>> > definitely problematic to me.
>> >
>> > To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
>> > to compare unequal with itself, yes?
>>
>> Nope.
>>
>> As you know, equality of arrays is implemented generically and based on
>> the equatable conformance of their elements. Therefore, two arrays of
>> equatable elements are equal iff the conforming implementation of
>> Equatable's == is true for all elements.
>>
>> > It is very clear that here we are working with a concrete FP type and
>> > not in a generic context, and thus all IEEE FP behavior should apply.
>>
>> I suppose that's one interpretation, but it's not the right one.
>>
>> If this were C++, it would be different, because of the way template
>> instantiation works: in a generic context like the == of Array, the
>> compiler would look up the syntactically-available == for the elements
>> and use that. But Swift is not like that; static lookup is done at the
>> point where Array's == is compiled, and it only finds the == that's
>> supplied by the Element's Equatable conformance.
>>
>> This may sound like an argument based on implementation details of the
>> language, and to some extent it is. But that is also the fundamental
>> nature of the Swift language (and one for which we get many benefits),
>> and it is hopeless to paper over it. For example, I can claim that all
>> doubles are equal to one another:
>>
>> 9> func == (lhs: Double, rhs: Double) -> Bool { return true }
>> 10> 4.0 == 1.0
>> $R2: Bool = true
>> 11> [4.0] == [1.0] // so the arrays should be equal too!
>> $R3: Bool = false
>>
>> Another way to look at this is that Array is not a numeric vector, and
>> won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
>> would be wrong for you to expect it to reflect the numeric properties of
>> its elements.
>>
>> >> This is a bump in the rug – push it down in one place, it pops up in
>> >> another. I feel like this proposal at least moves the bump to where
>> fewer
>> >> people will trip over it. I think it highly likely that the
>> intersection of
>> >> developers who understand enough about floating point to write truly
>> >> correct concrete code, but won’t know about or discover the
documented
>> >> difference in generic code, is far smaller than the set of people who
>> hit
>> >> problems with the existing behavior.
>> >>
>> >
>> > So, to extend this analogy, I'd rather say that the bump is not in the
>> rug
>> > [Comparable] but rather in a section of the floor [FP NaN]. The rug
might
>> > overlie the bump, but the bump will always be there and people will
find
>> it
>> > as they walk even if they don't immediately see it.
>>
>> Correct.
>>
>> > If we don't want people to trip over the bump while walking on the
>> > rug, one very good alternative, IMHO, is to shape the rug so that it
>> > doesn't cover the bump.
>>
>> At what cost?
>>
>> More specifically: why is it the right behavior, for our audience, to
>> trap when Equatable comparison happens to encounter NaN? Will this not
>> simply "crash" programs in the field that otherwise would have "just
>> worked?"
>>
>> > My purpose in exploring an alternative design is to see if it would be
>> > feasible for non-FP-aware comparison operators to refuse to compare
NaN,
>> > rather than giving different answers depending on context.
>>
>> So... to be clear, this is still different behavior based on context.
>> Is this not just as confusing a result?
>>
>> let nan = 0.0 / 0.0
>> print(nan == nan) // false
>> print([nan] == [nan]) // trap
>>
>
> No, in my alternative proposal:
>
> ```
> let nan = 0.0 / 0.0
> print(nan == nan) // trap
> print([nan] == [nan]) // trap
> print(nan &== nan) // false
> print([nan] &== [nan]) // false
> ```

Oh, that's an interesting approach. Now you are asking people to
translate the == in numeric code.

Yes. Specifically, in floating point code. I guess that's the part about
shaping the rug not to cover the bump. IEEE does not require `==` to be the
spelling of the quiet equality comparison operator, and it does
specifically describe a comparison operator that behaves identically to
what I propose as the trapping `==` (which, I believe, is actually spelled
in the IEEE standard as `==`).

I guess I'd want to hear what Steve

···

On Sat, Apr 22, 2017 at 5:51 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> > wrote:
>> on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
>> > On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < > >> > swift-evolution@swift.org> wrote:
>> >> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < > >> >> swift-evolution@swift.org> wrote:
>> >> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < > >> >> swift-evolution@swift.org> wrote:
>> >> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < > >> >> swift-evolution@swift.org> wrote:
Canon has to say about that.

It still begs all the questions I've asked above, though. Should I
repeat them?

>> > I now strongly believe that this may make for a design simultaneously
>> > _less_ complex *and* _more_ comprehensive (as measured by the
>> > flatness-of-rug metric).
>>
>> I'm certainly willing to discuss it, but so far it doesn't seem like
>> you've been willing to answer the central questions above.
>>
>> --
>> -Dave
>>

--
-Dave

>
>>
>>
>>
>>
>>
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that `XCTAssertEqual(resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different starting
>> point. If you start with a concrete algorithm on Int, then generalize it to
>> all Equatable types, then your algorithm will have unexpected behavior for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal as written promises FP-aware versions of these functions. That is to say, I would expect the standard library to supply an alternative implementation of equality for Array<T where T : FloatingPoint>.

Are you suggesting it implies that Array's Equatable conformance is implemented differently when T: FloatingPoint? Is it even possible to provide such an implementation given the current restriction that bans overlapping conformance? (If there is a technique for implementing something like this in Swift 4 I would love to learn it)

···

Sent from my iPad

On Apr 22, 2017, at 4:53 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:
on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:
> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:

>> This is a bump in the rug – push it down in one place, it pops up in
>> another. I feel like this proposal at least moves the bump to where fewer
>> people will trip over it. I think it highly likely that the intersection of
>> developers who understand enough about floating point to write truly
>> correct concrete code, but won’t know about or discover the documented
>> difference in generic code, is far smaller than the set of people who hit
>> problems with the existing behavior.
>>
>
> So, to extend this analogy, I'd rather say that the bump is not in the rug
> [Comparable] but rather in a section of the floor [FP NaN]. The rug might
> overlie the bump, but the bump will always be there and people will find it
> as they walk even if they don't immediately see it.

Correct.

> If we don't want people to trip over the bump while walking on the
> rug, one very good alternative, IMHO, is to shape the rug so that it
> doesn't cover the bump.

At what cost?

More specifically: why is it the right behavior, for our audience, to
trap when Equatable comparison happens to encounter NaN? Will this not
simply "crash" programs in the field that otherwise would have "just
worked?"

No, as I propose it, programs in the field would be automatically migrated to an alternative set of comparison operators `&==`, `&<`, etc. that would work exactly as `==`, `<`, etc. do today. I would quibble with the notion that all such generic algorithms currently "just work," but the result is that they would behave exactly as they do today and therefore would at least be no more broken.

Standard library changes to `sort` and other functions will make them "just work" with no distinguishable difference to the end user as compared to this proposal here. It would be an improvement over how the algorithms work today with NaN.

The major difference to the end user between what I propose and this proposal here will surface when _new_ code is written that uses `==` in the generic context, when working with types whose values may compare unordered. Since I propose `<=>` to return a value of type `Comparison?`, using the revised operator `==` is an assertion that the result of comparison is not unordered. A user is welcome to use `&==` or a custom predicate if that is not their intention.

> My purpose in exploring an alternative design is to see if it would be
> feasible for non-FP-aware comparison operators to refuse to compare NaN,
> rather than giving different answers depending on context.

So... to be clear, this is still different behavior based on context.
Is this not just as confusing a result?

  let nan = 0.0 / 0.0
  print(nan == nan) // false
  print([nan] == [nan]) // trap

> I now strongly believe that this may make for a design simultaneously
> _less_ complex *and* _more_ comprehensive (as measured by the
> flatness-of-rug metric).

I'm certainly willing to discuss it, but so far it doesn't seem like
you've been willing to answer the central questions above.

Clearly, I'm not understanding the central questions. Which ones have I left unanswered?
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

<snip>

>> > To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
>> > to compare unequal with itself, yes?
>>
>> Nope.
>>
>> As you know, equality of arrays is implemented generically and based on
>> the equatable conformance of their elements. Therefore, two arrays of
>> equatable elements are equal iff the conforming implementation of
>> Equatable's == is true for all elements.
>>
>> > It is very clear that here we are working with a concrete FP type and
>> > not in a generic context, and thus all IEEE FP behavior should apply.
>>
>> I suppose that's one interpretation, but it's not the right one.
>>
>> If this were C++, it would be different, because of the way template
>> instantiation works: in a generic context like the == of Array, the
>> compiler would look up the syntactically-available == for the elements
>> and use that. But Swift is not like that; static lookup is done at the
>> point where Array's == is compiled, and it only finds the == that's
>> supplied by the Element's Equatable conformance.
>>
>> This may sound like an argument based on implementation details of the
>> language, and to some extent it is. But that is also the fundamental
>> nature of the Swift language (and one for which we get many benefits),
>> and it is hopeless to paper over it. For example, I can claim that all
>> doubles are equal to one another:
>>
>> 9> func == (lhs: Double, rhs: Double) -> Bool { return true }
>> 10> 4.0 == 1.0
>> $R2: Bool = true
>> 11> [4.0] == [1.0] // so the arrays should be equal too!
>> $R3: Bool = false
>>
>> Another way to look at this is that Array is not a numeric vector, and
>> won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
>> would be wrong for you to expect it to reflect the numeric properties of
>> its elements.
>>
>
> I understand that's how the generic Array<T> would work, but the proposal
> as written promises FP-aware versions of these functions.

Where do you see that promise? If we said or even implied that, I
didn't review the text carefully enough.

This results in code that is explicitly designed to work with

FloatingPoint types getting the expected IEEE behaviour, while code that is
only designed to work with Comparable types (e.g. sort and Dictionary) gets
more reasonable total ordering behaviour.

To clarify: Dictionary and sort won’t somehow detect that they’re being

used with FloatingPoint types and use level 1 comparisons. Instead they
will unconditional[ly] use level 2 behaviour.

[...]

Some free functions will have <T: FloatingPoint> overloads to better

align with IEEE-754 semantics. This will be addressed in a follow-up
proposal. (example: min and max)

The implication I took away is that a follow-on proposal will align a much
greater swath of functions to IEEE-754 semantics. I did not realize you
meant _some_ free functions also meant that _only_ free functions would be
refined.

That is to say, I would expect the standard library to supply an
> alternative implementation of equality for Array<T where T :
> >.

And also for Dictionary? What do you expect to happen when Double is
used as a dictionary key and it happens to be NaN?

The proposal is very clear that `Dictionary` and `sort` will always use
level 2 comparison.

>> >> This is a bump in the rug – push it down in one place, it pops up
>> >> in another. I feel like this proposal at least moves the bump to
>> >> where
>> fewer
>> >> people will trip over it. I think it highly likely that the
>> intersection of
>> >> developers who understand enough about floating point to write truly
>> >> correct concrete code, but won’t know about or discover the
documented
>> >> difference in generic code, is far smaller than the set of people who
>> hit
>> >> problems with the existing behavior.
>> >>
>> >
>> > So, to extend this analogy, I'd rather say that the bump is not in the
>> rug
>> > [Comparable] but rather in a section of the floor [FP NaN]. The rug
might
>> > overlie the bump, but the bump will always be there and people will
find
>> it
>> > as they walk even if they don't immediately see it.
>>
>> Correct.
>>
>> > If we don't want people to trip over the bump while walking on the
>> > rug, one very good alternative, IMHO, is to shape the rug so that it
>> > doesn't cover the bump.
>>
>> At what cost?
>>
>> More specifically: why is it the right behavior, for our audience, to
>> trap when Equatable comparison happens to encounter NaN? Will this not
>> simply "crash" programs in the field that otherwise would have "just
>> worked?"
>
> No, as I propose it, programs in the field would be automatically
migrated
> to an alternative set of comparison operators `&==`, `&<`, etc. that
would
> work exactly as `==`, `<`, etc. do today.

Meaning, for floating point NaN &== NaN is false, and if you want to
write numeric code that accounts for NaN, you use &==.

OK, so... Is &== a protocol requirement, or a protocol extension, or
neither? If so, to which protocol is it attached?

Please allow me to refer you to a Gist:

In brief, it would be a protocol requirement on Comparable with a default
implementation. The rationale for its being on Comparable is given in the
text. I am not married to its being a requirement vs. an extension, but my
initial thought here is that there might be reason to provide an
alternative implementation in a conforming type, say for performance
reasons on Float.

> I would quibble with the notion that all such generic algorithms
> currently "just work,"

I never claimed they do! They don't, because Equatable.== for floating
point is not an equivalence relation. That's part of what we aim to
fix.

You are proposing to fix that same problem a different way, one that leaves
NaNs a bit out-in-the-cold (not necessarily bad), but also explicitly
modifies generic algorithms so they continue to silently produce
unspecified results (bad!)

To clarify, no, I would not have the stdlib's generic algorithms continue
to produce unspecified results. I propose changes to them which align their
behavior with what you and Ben have proposed.

Any automatically migrated third-party generic code would indeed continue
to exhibit the same behavior as in Swift 3--but not only do I not consider
that to be a problem, I consider it to be a requirement of source
compatibility which is absolutely essential.

It would not, however, be _invisible_ to the reader of the generic
algorithm. The use of my proposed `&==` in a generic context should stand
out and prompt re-evaluation. That is to say, by using a different
spelling, we would have a visible hint in the code that a generic algorithm
may produce unspecified results with NaN.

but the result is that they would behave exactly as they do today and
> therefore would at least be no more broken.

If that's all we acheive, we should do nothing.

I should hope that it's not all we achieve. But, consider the following two
alternatives: migrated code exhibits identical behavior to Swift 3, or
migrated code silently exhibits different behavior that is "fixed." I am
very disturbed by the possibility of the latter. It is the only part of
this proposal that keeps me up at night.

As it turns out, some people really do understand how floating point
comparison works, and they might have even carefully written code that
behaves correctly, relying on the current behavior when things are
compared. Please don't "fix" that code. If an array of type [Float] starts
to distinguish between +0.0 and -0.0 as you propose, I'm quite sure that
there is at least some code of my own that will be quite broken.

Standard library changes to `sort` and other functions will make them
> "just work" with no distinguishable difference to the end user as
> compared to this proposal here.

I'm sorry, I don't know what "this proposal here" means. Is that yours
or the one Ben and I offered? It's certainly different from the results
of our proposal.

The big problem with our proposal, AFAICT, is that

    floatsIncludingNaNs.sort()

works but

    floatsIncludingNaNs.sort(>)

does not. That is a real problem, but it *is* a difference from the
current behavior, where neither one works.

Hmm, I get the sense that some of my replies to you have been lost. I have
explicitly proposed a design where `floatsIncludingNaNs.sort()` produces
the same behavior as what is proposed by you and Ben. I'd like to refer you
again to the fleshed out Gist:

It would be an improvement over how the algorithms work today with
> NaN.
>
> The major difference to the end user between what I propose and this
> proposal here will surface when _new_ code is written that uses `==` in
the
> generic context, when working with types whose values may compare
> unordered. Since I propose `<=>` to return a value of type `Comparison?`,
> using the revised operator `==` is an assertion that the result of
> comparison is not unordered. A user is welcome to use `&==` or a custom
> predicate if that is not their intention.

The problem with this is that there's still no simple way to get an
equivalence relation or a total order over all Doubles, including NaNs.

There is. Given two values x and y, `x &< y || (y <=> y) == nil` is
identical to the `<` that you propose.

Now, I'm totally willing to have the discussion about how NaNs have no

business being used as dictionary keys, or sort keys, or searched for,
or any of the other things we do with day-to-day values. That's not
something I really have an opinion on, yet.

I would not assert that NaN has no business being used here; again, my
alternative design accommodates all of these use cases.

Where we differ is that, in the case of a generic algorithm, my alternative
design would result in the author of that algorithm either explicitly
accommodating the presence of unordered values or asserting their absence.
It is not an avoidable problem--this is the bump in the rug that cannot be
smoothed out.

I would posit that it is not possible to write an arbitrary generic
algorithm that (a) compares floating point values; (b) doesn't account for
NaN; and (c) behaves correctly, where correctly here means that it returns
what an average user would expect who is not thinking of floating point
comparison foibles. For instance, generic `max` produces what to the
average user is nonsense if NaN compares greater than everything.

I am, however, concerned

that ordinary valid computations can lead to NaN and that allowing the
appearance of a NaN to turn into a trap much later in the program, where
it is finally compared with something, is not a behavior that would work
for ordinary users.

>> My purpose in exploring an alternative design is to see if it would be
>> > feasible for non-FP-aware comparison operators to refuse to compare
NaN,
>> > rather than giving different answers depending on context.
>>
>> So... to be clear, this is still different behavior based on context.
>> Is this not just as confusing a result?
>>
>> let nan = 0.0 / 0.0
>> print(nan == nan) // false
>> print([nan] == [nan]) // trap
>>
>> > I now strongly believe that this may make for a design simultaneously
>> > _less_ complex *and* _more_ comprehensive (as measured by the
>> > flatness-of-rug metric).
>>
>> I'm certainly willing to discuss it, but so far it doesn't seem like
>> you've been willing to answer the central questions above.
>>
>
> Clearly, I'm not understanding the central questions. Which ones have I
> left unanswered?

Again:

  Why is it the right behavior, for our audience, to trap when Equatable
  comparison happens to encounter NaN?

There are three possibilities (that I know of) when an equatable comparison
`==` encounters NaN:

* unspecified behavior (the current situation)
* a default behavior (as proposed by you and Ben, that would be ordering
NaN after all other values)
* trapping (as proposed by me)

I take it as given that you do not need to be convinced why unspecified
behavior is inferior to the alternatives. As to why trapping is superior to
a default behavior, I return to what I talk about above:

Rhetorical question--do you think that there is any design for Comparable
that would allow someone to compare floating point values *without* knowing
about the existence of NaN in such a way that an arbitrary generic
algorithm would behave as expected for a user who isn't thinking about
floating point comparison?

As I point out above, ordering NaN after all other values works for `sort`
but doesn't work so well for `max`. You can say that you'll provide a
special floating point `max`, but I can do you one better with a design
where the _generic algorithm_ and not the _comparable type_ sorts out what
happens with unordered values. In such a design, both generic `sort` and
generic `max` can offer fully specified, sensible behavior *without*
special versions for floating point.

So, having said all of that, I return to address the question directly.
Let's consider where a user might encounter `==` unexpectedly trapping on
NaN:

* The user is writing FP code, intending to use FP comparisons, and hasn't
heard about the change in spelling. He or she is given immediate feedback
and uses the corresponding operators `&==`, etc. This is a one-time
learning experience.

* The user is authoring a generic algorithm and using `==`. Trapping is
optimal because either they will test their algorithm with floating point
NaN and then consider how to handle that special case, or they will not
test their algorithm and `==` is effectively a precondition that the
algorithm will not encounter NaN, which would be an untested scenario. If,
on the other hand, a default behavior is instead what occurs, it may not be
unspecified behavior to _you_ the author of this proposal for Comparable,
but it certainly would be behavior that has never been reasoned through by
the author of the generic algorithm.

* The user is calling a generic algorithm not designed for handling NaN
using a FP argument that is NaN. I believe that trapping is still the
correct behavior because silently proceeding with a result that the author
of the generic algorithm has never tested or thought about is potentially
more harmful than the user of the algorithm getting immediate feedback that
the algorithm has not been tested with NaN. For instance, how can I tell if
stdlib `max` is "NaN-ready"? Well, if `max` is not NaN-ready, in my
proposed design `max(.nan)` will trap right away; in yours, one must
inspect the result and consider whether the behavior is what is intended
and appropriate, which should principally be the job of the author of the
generic algorithm.

···

On Sat, Apr 22, 2017 at 6:37 PM, Dave Abrahams <dabrahams@apple.com> wrote:

I started writing a big long reply last night, but I accidentally deleted the draft instead of saving it. Oh well, it was kinda rambly anyway. Here're my two (and a half?) main points:

1.a) No
1.b) Although maybe we should, since this is the current behavior in playgrounds:
var dict = [Double:String]()
dict[.nan] = "Foo"
dict.description // "[nan: "Foo”]” Yay!, it was inserted!
dict[.nan]?.description // “nil" Wait, what?
dict[.nan] = "Bar"
dict.description // "[nan: "Foo", nan: "Bar”]” Hey, how come two values have the same key?
dict[.nan]?.description // “nil" And why can’t I retrieve either of them?
let values: [String?] = dict.keys.map { dict[$0]?.description }
values.description // "[nil, nil]” I can’t even use the keys given by the dictionary itself to get my values?!?
2) Maybe it would be helpful to think of Float and Double as sorta being their own Optional type with an extra payload to let a pseudo-“.none" explain why it's not a valid value. I’m not suggesting that we actually redefine Double as “Optional<Real64>” (even if we could get the extra payload), but if we think of them that way, it might simplify how we approach this.

- Dave Sweeris

···

On Apr 22, 2017, at 4:14 PM, Dave Abrahams <dabrahams@apple.com> wrote:

on Sat Apr 22 2017, David Sweeris <davesweeris-AT-mac.com> wrote:

Maybe we should make Float/Double conform to
"IEEE754Comparable"/"IEEE754Equatable" instead of
"Comparable"/"Equatable". Then it's all a moot point since floating
point types wouldn't end up in the same generic functions as other
comparable types.

(Not sure if I'm serious)

Do you want to ban

Dictionary<Float,String>

? That would be one consequence.

As discussed a long time ago, and as I raised here in an earlier reply in
this thread, a total order over floating point values is almost never what
you want. It distinguishes between +0.0 and -0.0, between +NaN and -NaN,
between signaling NaN and quiet NaN, between different NaN payloads, and
for IEEE Decimal (not yet implemented in Swift), between different
representations of the same floating point value based on their exponent.
Swift already exposes this function as `isTotallyOrdered` and it is, well,
very niche.

···

On Wed, Apr 26, 2017 at 2:08 AM, Jaden Geller <jaden.geller@gmail.com> wrote:

On Apr 25, 2017, at 11:50 PM, Jonathan Hull <jhull@gbis.com> wrote:

On Apr 25, 2017, at 9:34 PM, Jaden Geller <jaden.geller@gmail.com> wrote:

On Apr 25, 2017, at 8:28 PM, Jonathan Hull via swift-evolution < > swift-evolution@swift.org> wrote:

On Apr 25, 2017, at 7:17 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

I'll refer you to Steve Canon's answer on StackOverflow: http://
stackoverflow.com/questions/1565164/what-is-the-rationale-
for-all-comparisons-returning-false-for-ieee754-nan-values/1573715#1573715

I get what he is saying about not being able to suddenly change C’s
definition to the opposite boolean value (even if they would have designed
it differently for modern systems). We aren’t C though. And nil is a
different name than NaN. This gives us a way to make a change without
breaking conformance.

I really like this idea, but it’s not true to say this doesn’t break
IEEE-compliance if what we expose as NaN no longer compares not equal to
itself.

I would argue that we aren’t actually exposing NaN, so much as we are
exposing something which is different, but stays in sync with it. NaN is
still there in the format under the surface, but you wouldn’t be able to
access it directly (only indirectly).

The problem is that the IEEE spec requires that certain operations return
NaN. Either nil is NaN in this model, and we’re breaking the spec for
equality, or nil is different from NaN in this model, and we’re breaking
the spec for when NaN should be produced.

It is a bit of a magic trick. Whenever the value is NaN, we only let you
access ‘.none’ instead of the value. So, if you were able to directly
compare NaN with NaN, it would be false (or unordered), but you can’t get
ahold of them to do that comparison.

I don’t find this argument compelling. Again, if it doesn’t act like NaN,
it isn’t following spec. It doesn’t matter that it has the same bit
representation.

The important thing to me is binary compatibility. Algorithms need to be
rewritten a bit anyway when moving them into swift, so that isn’t a big
deal to write it for nil instead of NaN… especially when the compiler helps
you due to the optional. But data, which might contain NaN, needs to be
read/written in an interchangeable format. That is why I suggest having
Optional do the lifting to match NaN. Anytime it crosses a boundary which
strips the wrapper, it will just work (as long as we import as Double?).

I think this would be a cool idea were it not that so many people are
against breaking the spec. This trick did work well for UnsafePointer, but
the circumstances were different.

Anyway, I would be partially in favor of this idea, but I don’t think
there’s any way the community as a whole is going to be on-board.

Bottom line is, whatever it's designed for, it's here to stay. I'm *not*
on any IEEE committee; I'm not qualified to design an alternative universe
of floating point types; and the Swift evolution mailing list (or even
Swift itself) is not the place to design one. I and others rely on Swift
floating point types to be IEEE-complaint, so that's where we start the
discussion here about Comparable.

It *is* IEEE compliant (or at least the same amount we currently are),
what is different is the way we interface with that in code. You can think
of Swift Double as a simple (runtime-cost-free) wrapper around the
compiler’s double that limits how you can touch it (in order to provide
additional safety guarantees). Sounds very swifty to me… we do similar
things with other C constructs using various wrappers. We aren’t getting
rid of NaN behind the scenes, just packaging it’s use in a way which aligns
with Swift as a whole… What changes is the programer’s mental model. The
bits are exactly the same.

If the naming is what bothers you, we could even just create a “new” Swift
type that is this wrapper, and then discourage the use of Double outside of
places where NaN is needed. I feel like NaN is actually needed in
relatively few places though…

The programmer (mental) model would be that Swift Double just doesn’t
have NaN, and anywhere where you would normally return NaN, you return nil
instead.

Look, you can already use Double? today. There is no barrier to trying it
out for yourself. However, `Double?.none` doesn't mean the same thing as
`Double.nan`. The former indicates that _there is no value_; the latter
indicates that there _is_ a value, it's just _not a number_. Suppose I
parse a list of numbers into an array. If I ask for [Double].last and get
nil, it's telling me that _there are no elements in the array_. If I get
.nan, it's telling me that there *is* a value, it just wasn't a number. In
the former case, I'd do nothing. In the latter case, I might prompt the
user: this isn't a number!

However, the property of using NaN’s bits to represent nil let’s us
inter-op seamlessly with C and ObjC (or any other language’s) code. They
just treat it as a double with NaN as normal (including NaN != NaN) and we
interface with it as ‘Double?'

I'm going to sound like a broken record, now. Whether floating point types
in Swift conform to IEEE standards is _not_ up for discussion, afaict;
that's simply a given. Now, around that constraint, we're trying to design
a revised Comparable protocol. Code written today for floating point work
expects NaN != NaN. That is just something that is and will forever be. We
break source compatibility if we change that.

As I said above, they *do* conform to those standards… they just don’t
expose the full capabilities directly to the end programmer. The
capabilities are still there, you just have to be more explicit about their
use.

I could be wrong, but I believe that in the current version of Swift, the
result of doing comparisons with NaN is actually undefined at the moment…
so it isn’t breaking source compatibility with the defined language. Just
with code which is using implementation artifacts…

I think you’re incorrect. It says in the docs for NaN that it always
compares false with itself.

That’s too bad. I was looking in the language guide. I guess it would be
a breaking change to change Float/Double. We could still use the idea
though, we would just have to add the wrapper as a new type with a
different name.

I’d be a lot more in favor of this as compared to the Optional suggestion
above, but I’m worried about the API surface impact. It also does not solve
our `Equatable` and `Comparable` issues…

I guess we could just say `PotentiallyUnknown<Double>` doesn’t conform to
either, and you need to unwrap if you’d like to compare. We could also add
optional-returning comparison functions without creating a formal protocol.
I think this would all be pretty reasonable.

I would be in favor of renaming Float/Double to something like
CFloat/CDouble, and then reusing the names for the wrappers. Not sure if I
could convince the powers that be of that though…

We could also write `typealias CDouble = PartiallyUnknown<Double>` so it’s
less painful to work with. I’d be very happy if `Double` trapped.

I am not sure how much code actually uses NaN in this way in the real
world.

C code might require some simple changes to work in Swift, but as you
argued passionately before, that is already to be expected. We shouldn’t
have the expectation of copy/pasting C code without thinking through the
differences in &+, etc...

This did not always work correctly, if I recall, but it does now and it's

an intentional part of the design. However, NaN must compare not equal to
every NaN. These could not be more different properties. It seems quite
absurd on its face that we might want NaN to compare equal to a value of
type `Optional<UIViewController>`.

Is there an algorithm that requires NaN != NaN that couldn’t be
reasonably rewritten to handle nil/optionals instead?

I don't need an algorithm to show you the problem. See this expression: `0
* Double.infinity == .infinity * 0` correctly evaluates to false. Zero
times infinity is simply not equal to infinity times zero. You're
suggesting a design where the result would be true, and that simply won't
fly, because it's just not true.

IEEE 754 says that the result of any comparison with NaN should be
*undefined*. C’s implementation is the one who brings us this idea that
NaN != NaN. The *correct* evaluation according to 754 would be
‘undefined’. We aren’t breaking 754, just breaking away from a C
convention… in a way which is very much in line with how Swift breaks away
from other C conventions.

I don’t think this is true. I got excited for a minute and looked it up.
Where do you see that comparison behavior is undefined?

Oh, sorry, I didn’t mean that the behavior is undefined… I used the wrong
word (freudian slip). I meant to say that the correct evaluation is
*unordered*. Four possibilities are defined in the spec: GreaterThan,
LessThan, Equal, and Unordered. NaN is always unordered when compared with
anything.

From the spec:

For every supported arithmetic format, it shall be possible to compare one
floating-point datum to another in that format (see 5.6.1). Additionally,
floating-point data represented in different formats shall be comparable as
long as the operands’ formats have the same radix.

Four mutually exclusive relations are possible: less than, equal, greater
than, and unordered. The last case arises when at least one operand is
NaN. Every NaN shall compare unordered with everything, including itself.
Comparisons shall ignore the sign of zero (so +0 = −0). Infinite operands
of the same sign shall compare equal.

And as I read further, It does appear I was wrong about NaN != NaN (when
doing a partial order). I had somehow missed it in my first read-through
In the 2008 version of the spec, it goes on to say:

Languages define how the result of a comparison shall be delivered, in one
of two ways: either as a relation identifying one of the four relations
listed above, or as a true-false response to a predicate that names the
specific comparison desired.

Which means that we should have 4 functions: ==, <, >, and isUnordered…
where isUnordered is the only one which returns true for NaN

That sounds a lot more like what I expected, cool.

One loophole is that it gives alternate names for those functions which we
could use for the official behavior (and still use < and == for our strict
total order):
• compareQuietEqual
• compareQuietGreater
• compareQuietLess
• compareQuietUnordered

Are you suggesting that `<` and friends trap? Is this still in the
type-system aware NaN world, or is this an entirely different option you’re
exploring?

Also, we should all read section 5.10, as it seems to give a canonical
answer of how to do a total order for 754:

totalOrder(x, y) imposes a total ordering on canonical members of the
format of x and y:

   1.

   a) If x < y, totalOrder(x, y) is true.
   2.

   b) If x > y, totalOrder(x, y) is false.
   3.

   c) Ifx=y:
   1.

      1) totalOrder(−0, +0) is true.
      2.

      2) totalOrder(+0, −0) is false.
      3.

      3) If x and y represent the same floating-point datum:

i) If x and y have negative sign,
totalOrder(x, y) is true if and only if the exponent of x ≥ the exponent
of y

ii) otherwise
totalOrder(x, y) is true if and only if the exponent of x ≤ the exponent
of y.

d) If x and y are unordered numerically because x or y is NaN:

   1.

   1) totalOrder(−NaN, y) is true where −NaN represents a NaN with
   negative sign bit and y is a

   floating-point number.
   2.

   2) totalOrder(x, +NaN) is true where +NaN represents a NaN with
   positive sign bit and x is a floating-point number.
   3.

   3) If x and y are both NaNs, then totalOrder reflects a total
   ordering based on:
   1.

      i) negative sign orders below positive sign

Oh god, I didn’t realize that NaN could be signed…

   1.
      1.
      2.

      ii) signaling orders below quiet for +NaN, reverse for −NaN

   iii) lesser payload, when regarded as an integer, orders below greater
   payload for +NaN, reverse for −NaN.

Neither signaling NaNs nor quiet NaNs signal an exception. For canonical x
and y, totalOrder(x, y) and totalOrder( y, x) are both true if x and y are
bitwise identical.

Here we see NaN == NaN when doing a total order (as long as the payloads
are the same).

Thanks,
Jon

Cheers,
Jaden Geller

Just throwing a idea that came to me while reading this discussion (hopefully not duplicating something that was already suggested)...

We could introduce `FiniteFloat` and `FiniteDouble` types that would disallow NaN and infinity. Computations creating NaN or infinity would trap with these types, mimicking what happens with integers in Swift. Converting a `Double` to `FiniteDouble` would be achieved through a `finiteValue` property like this:

  var a = 1.0
  var b = a.finiteValue!

where the `finiteValue` property returns a `FiniteDouble?` that is `nil` for NaN or infinity values. The finite type variant can conform to Equatable with no issue. The regular one that allows NaNs and infinity would then have less pressure to conform to Equatable. Just request the finite value whenever you need it:

  dict[a.finiteValue!] = "a"

I understand that this goes a bit beyond solving the problem with comparison which is caused by NaNs and not by infinity values. But it's quite easy to explain what "finite" means and it follows how integers works. It also has the interesting property of guarantying a finite value for APIs that needs that, which I believe is more frequent than APIs that require only a non-NaN value. Hence why I'm suggesting FiniteDouble and not NonNaNDouble.

···

--
Michel Fortin
https://michelf.ca

I don't believe it's possible, but there is compiler magic being proposed
to make this protocol stick together, so we are not bound by the limits of
Swift 4-expressible designs.

···

On Sat, Apr 22, 2017 at 7:02 PM, Matthew Johnson <matthew@anandabits.com> wrote:

Sent from my iPad

On Apr 22, 2017, at 4:53 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:

On Sat, Apr 22, 2017 at 4:14 PM, Dave Abrahams <dabrahams@apple.com> > wrote:

on Tue Apr 18 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

> On Tue, Apr 18, 2017 at 10:40 AM, Ben Cohen via swift-evolution < >> > swift-evolution@swift.org> wrote:
>
>>
>> On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 17, 2017, at 9:07 AM, Joe Groff via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>>
>> On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution < >> >> swift-evolution@swift.org> wrote:
>>
>> For example, I expect `XCTAssertEqual<T : FloatingPoint>(_:_:)` to be
>> vended as part of XCTest, in order to make sure that
`XCTAssertEqual(resultOfComputation,
>> Double.nan)` always fails.
>>
>>
>> Unit tests strike me as an example of where you really *don't* want
level
>> 1 comparison semantics. If I'm testing the output of an FP operation, I
>> want to be able to test that it produces nan when I expect it to, or
that
>> it produces the right zero.
>>
>>
>> I find it very concerning that == will have different results based on
>> concrete vs generic type parameters. This can only lead to significant
>> confusion down the road. I’m highly concerned about situations where
>> taking a concrete algorithm and generalizing it (with generics) will
change
>> its behavior.
>>
>>
>> It is already the case that you can start with a concrete algorithm,
>> generalize it, and get confusing results – just with a different
starting
>> point. If you start with a concrete algorithm on Int, then generalize
it to
>> all Equatable types, then your algorithm will have unexpected behavior
for
>> floats, because these standard library types fail to follow the rules
>> explicitly laid out for conforming to Equatable.
>>
>> This is bad. Developers need to be able to rely on those rules. The
>> standard library certainly does:
>>
>> let a: [Double] = [(0/0)]
>> var b = a
>>
>> // true, because fast path buffer pointer comparison:
>> a == b
>>
>> b.reserveCapacity(10) // force a reallocation
>>
>> // now false, because memberwise comparison and nan != nan,
>> // violating the reflexivity requirement of Equatable:
>> a == b
>>
>>
>> Maybe we could go through and special-case all the places in the
standard
>> library that rely on this, accounting for the floating point behavior
>> (possibly reducing performance as a result). But we shouldn't expect
users
>> to.
>>
>
> I was not thinking about the issue illustrated above, but this is
> definitely problematic to me.
>
> To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
> to compare unequal with itself, yes?

Nope.

As you know, equality of arrays is implemented generically and based on
the equatable conformance of their elements. Therefore, two arrays of
equatable elements are equal iff the conforming implementation of
Equatable's == is true for all elements.

> It is very clear that here we are working with a concrete FP type and
> not in a generic context, and thus all IEEE FP behavior should apply.

I suppose that's one interpretation, but it's not the right one.

If this were C++, it would be different, because of the way template
instantiation works: in a generic context like the == of Array, the
compiler would look up the syntactically-available == for the elements
and use that. But Swift is not like that; static lookup is done at the
point where Array's == is compiled, and it only finds the == that's
supplied by the Element's Equatable conformance.

This may sound like an argument based on implementation details of the
language, and to some extent it is. But that is also the fundamental
nature of the Swift language (and one for which we get many benefits),
and it is hopeless to paper over it. For example, I can claim that all
doubles are equal to one another:

  9> func == (lhs: Double, rhs: Double) -> Bool { return true }
10> 4.0 == 1.0
$R2: Bool = true
11> [4.0] == [1.0] // so the arrays should be equal too!
$R3: Bool = false

Another way to look at this is that Array is not a numeric vector, and
won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
would be wrong for you to expect it to reflect the numeric properties of
its elements.

I understand that's how the generic Array<T> would work, but the proposal
as written promises FP-aware versions of these functions. That is to say, I
would expect the standard library to supply an alternative implementation
of equality for Array<T where T : FloatingPoint>.

Are you suggesting it implies that Array's Equatable conformance is
implemented differently when T: FloatingPoint? Is it even possible to
provide such an implementation given the current restriction that bans
overlapping conformance? (If there is a technique for implementing
something like this in Swift 4 I would love to learn it)

754 does require a `==` that “raises exceptions", but “raise exception” has a very specific meaning in the 754 standard that does not align up with the Swift notion of “trap". In particular, “default exception handling” under 754 is to set a sticky flag that may be queried later, not to halt execution. This is exactly what the current Swift `==` operator does at the hardware level on arm64 and x86, though the flags are not accurately modeled by LLVM and there are no Swift bindings for manipulating the floating-point environment [yet] either, so the hardware flag is not especially useful.

I’m just catching up with this discussion because my daughter was born a couple days ago, but my quick reaction to `&==` is that it would make me quite nervous to have `==` not bound to 754-equals as it is in essentially every other language. In particular, I worry about the risk of people porting numerical code that depends on isnan(x) <—> !(x < y) in non-obvious ways that they are unlikely to test. I’ll try to follow up with more detailed thoughts tomorrow.

– Steve

···

On Apr 22, 2017, at 6:55 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

Yes. Specifically, in floating point code. I guess that's the part about shaping the rug not to cover the bump. IEEE does not require `==` to be the spelling of the quiet equality comparison operator, and it does specifically describe a comparison operator that behaves identically to what I propose as the trapping `==` (which, I believe, is actually spelled in the IEEE standard as `==`).

<snip>

>> > To be clear, this proposal promises that `[0 / 0 as Double]` will be
made
>> > to compare unequal with itself, yes?
>>
>> Nope.
>>
>> As you know, equality of arrays is implemented generically and based on
>> the equatable conformance of their elements. Therefore, two arrays of
>> equatable elements are equal iff the conforming implementation of
>> Equatable's == is true for all elements.
>>
>> > It is very clear that here we are working with a concrete FP type and
>> > not in a generic context, and thus all IEEE FP behavior should apply.
>>
>> I suppose that's one interpretation, but it's not the right one.
>>
>> If this were C++, it would be different, because of the way template
>> instantiation works: in a generic context like the == of Array, the
>> compiler would look up the syntactically-available == for the elements
>> and use that. But Swift is not like that; static lookup is done at the
>> point where Array's == is compiled, and it only finds the == that's
>> supplied by the Element's Equatable conformance.
>>
>> This may sound like an argument based on implementation details of the
>> language, and to some extent it is. But that is also the fundamental
>> nature of the Swift language (and one for which we get many benefits),
>> and it is hopeless to paper over it. For example, I can claim that all
>> doubles are equal to one another:
>>
>> 9> func == (lhs: Double, rhs: Double) -> Bool { return true }
>> 10> 4.0 == 1.0
>> $R2: Bool = true
>> 11> [4.0] == [1.0] // so the arrays should be equal too!
>> $R3: Bool = false
>>
>> Another way to look at this is that Array is not a numeric vector, and
>> won't be one no matter what you do ([1.0] + [2.0] => [1.0, 2.0]). So it
>> would be wrong for you to expect it to reflect the numeric properties of
>> its elements.
>>
>
> I understand that's how the generic Array<T> would work, but the proposal
> as written promises FP-aware versions of these functions.

Where do you see that promise? If we said or even implied that, I
didn't review the text carefully enough.

···

on Sat Apr 22 2017, Xiaodi Wu <xiaodi.wu-AT-gmail.com> wrote:

On Sat, Apr 22, 2017 at 6:37 PM, Dave Abrahams <dabrahams@apple.com> wrote:

,----

> This results in code that is explicitly designed to work with
> FloatingPoint types getting the expected IEEE behaviour, while code that is
> only designed to work with Comparable types (e.g. sort and Dictionary) gets
> more reasonable total ordering behaviour.

`----

Whether that supports your case depends on what you think “explicitly
designed to work with FloatingPoint types” means, but to be fair, I
can't really fault anyone for thinking their use of Array<Double>
comparison and its current results with NaN constitutes an explicit
design choice on their part, even though the result is formally
unspecified.

,----

> To clarify: Dictionary and sort won’t somehow detect that they’re being
> used with FloatingPoint types and use level 1 comparisons. Instead they
> will unconditional[ly] use level 2 behaviour.

`----

That one would seem to support my case, no?

,----

> Some free functions will have <T: FloatingPoint> overloads to better
> align with IEEE-754 semantics. This will be addressed in a follow-up
> proposal. (example: min and max)

`----

Hmm, I think I overlooked that passage and need Ben to remind me what we
were thinking there.

The implication I took away is that a follow-on proposal will align a
much greater swath of functions to IEEE-754 semantics. I did not
realize you meant _some_ free functions also meant that _only_ free
functions would be refined.

OK, understandable.

> That is to say, I would expect the standard library to supply an
> alternative implementation of equality for Array<T where T :
> >.

And also for Dictionary? What do you expect to happen when Double is
used as a dictionary key and it happens to be NaN?

The proposal is very clear that `Dictionary` and `sort` will always use
level 2 comparison.

Yes, but I'm not asking about my own proposal :-). My question is, what
are *your* expectations for dictionaries or sets, specifically for the
insertion of NaN keys and for comparison of whole collections?

>> >> This is a bump in the rug – push it down in one place, it pops up
>> >> in another. I feel like this proposal at least moves the bump to
>> >> where
>> fewer
>> >> people will trip over it. I think it highly likely that the
>> intersection of
>> >> developers who understand enough about floating point to write truly
>> >> correct concrete code, but won’t know about or discover the
documented
>> >> difference in generic code, is far smaller than the set of people who
>> hit
>> >> problems with the existing behavior.
>> >>
>> >
>> > So, to extend this analogy, I'd rather say that the bump is not in the
>> rug
>> > [Comparable] but rather in a section of the floor [FP NaN]. The rug
might
>> > overlie the bump, but the bump will always be there and people will
find
>> it
>> > as they walk even if they don't immediately see it.
>>
>> Correct.
>>
>> > If we don't want people to trip over the bump while walking on the
>> > rug, one very good alternative, IMHO, is to shape the rug so that it
>> > doesn't cover the bump.
>>
>> At what cost?
>>
>> More specifically: why is it the right behavior, for our audience, to
>> trap when Equatable comparison happens to encounter NaN? Will this not
>> simply "crash" programs in the field that otherwise would have "just
>> worked?"
>
> No, as I propose it, programs in the field would be automatically
migrated
> to an alternative set of comparison operators `&==`, `&<`, etc. that
would
> work exactly as `==`, `<`, etc. do today.

Meaning, for floating point NaN &== NaN is false, and if you want to
write numeric code that accounts for NaN, you use &==.

OK, so... Is &== a protocol requirement, or a protocol extension, or
neither? If so, to which protocol is it attached?

Please allow me to refer you to a Gist:
An alternative design for Comparable · GitHub

In brief, it would be a protocol requirement on Comparable with a default
implementation. The rationale for its being on Comparable is given in the
text.

I'm sorry, I've seen your gist; I still can't find this rationale.

I am not married to its being a requirement vs. an extension, but my
initial thought here is that there might be reason to provide an
alternative implementation in a conforming type, say for performance
reasons on Float.

> I would quibble with the notion that all such generic algorithms
> currently "just work,"

I never claimed they do! They don't, because Equatable.== for floating
point is not an equivalence relation. That's part of what we aim to
fix.

You are proposing to fix that same problem a different way, one that leaves
NaNs a bit out-in-the-cold (not necessarily bad), but also explicitly
modifies generic algorithms so they continue to silently produce
unspecified results (bad!)

To clarify, no, I would not have the stdlib's generic algorithms continue
to produce unspecified results. I propose changes to them which align their
behavior with what you and Ben have proposed.

OK, I guess I must've misunderstood your earlier statements.

So this, IMO, is not tenable, at least in its current form:

,----

The default implementation for contains, elementsEqual, split, and
starts(with:) on Sequence where Iterator.Element : Equatable, and for
index(of:) on Collection where Iterator.Element : Equatable, will
(notionally) use the following predicate:

{
  ($0 &== $1) || (
    ($0 <=> $1) == .none &&
    ($0 <=> $0) == .none &&
    ($1 <=> $1) == .none)
}

`----

The first problem here is that I can't figure out what this means and I
doubt any normal user could either. How would this change in semantics
be reflected in the documentation for these algorithms? How would you
describe their requirements and results? All these algorithms currently
have simple, understandable descriptions.

Secondarily, I can't expect any generic algorithm author to understand
what he's getting with this.

Any automatically migrated third-party generic code would indeed continue
to exhibit the same behavior as in Swift 3--but not only do I not consider
that to be a problem, I consider it to be a requirement of source
compatibility which is absolutely essential.

Well, we have not promised to preserve unspecified behaviors, so
technically we can handle this either way. And we all seem to be agreed
that any code that uses, e.g., the default sort(), *will* change its
behavior with respect to NaN. So this is really a matter of degree.

It would not, however, be _invisible_ to the reader of the generic
algorithm. The use of my proposed `&==` in a generic context should stand
out and prompt re-evaluation. That is to say, by using a different
spelling, we would have a visible hint in the code that a generic algorithm
may produce unspecified results with NaN.

That's a very nice feature.

but the result is that they would behave exactly as they do today and
therefore would at least be no more broken.

If that's all we acheive, we should do nothing.

I should hope that it's not all we achieve. But, consider the following two
alternatives: migrated code exhibits identical behavior to Swift 3, or
migrated code silently exhibits different behavior that is "fixed."

I think we're both agreed that the latter *will* happen with

  sort(floatsContainingNaN)

or

   floatsContainingNaN.contains(.NaN)

I am very disturbed by the possibility of the latter. It is the only
part of this proposal that keeps me up at night.

I agree it's concerning, but I don't think fixing just part of this
problem is going to help either ;-).

As it turns out, some people really do understand how floating point
comparison works, and they might have even carefully written code that
behaves correctly, relying on the current behavior when things are
compared. Please don't "fix" that code. If an array of type [Float] starts
to distinguish between +0.0 and -0.0 as you propose, I'm quite sure that
there is at least some code of my own that will be quite broken.

yep, sadly. But IIUC that's inevitable.

> Standard library changes to `sort` and other functions will make them
> "just work" with no distinguishable difference to the end user as
> compared to this proposal here.

I'm sorry, I don't know what "this proposal here" means. Is that yours
or the one Ben and I offered? It's certainly different from the results
of our proposal.

The big problem with our proposal, AFAICT, is that

    floatsIncludingNaNs.sort()

works but

    floatsIncludingNaNs.sort(>)

does not. That is a real problem, but it *is* a difference from the
current behavior, where neither one works.

Hmm, I get the sense that some of my replies to you have been lost. I have
explicitly proposed a design where `floatsIncludingNaNs.sort()` produces
the same behavior as what is proposed by you and Ben. I'd like to refer you
again to the fleshed out Gist:

An alternative design for Comparable · GitHub

Sorry I misread your earlier remarks. I don't think I missed them.

> It would be an improvement over how the algorithms work today with
> NaN.
>
> The major difference to the end user between what I propose and
> this proposal here will surface when _new_ code is written that
> uses `==` in the generic context, when working with types whose
> values may compare unordered. Since I propose `<=>` to return a
> value of type `Comparison?`, using the revised operator `==` is an
> assertion that the result of comparison is not unordered. A user is
> welcome to use `&==` or a custom predicate if that is not their
> intention.

The problem with this is that there's still no simple way to get an

                                                  ^^^^^^

equivalence relation or a total order over all Doubles, including NaNs.

There is. Given two values x and y, `x &< y || (y <=> y) == nil` is
identical to the `<` that you propose.

Err, I rest my case?

Now, I'm totally willing to have the discussion about how NaNs have no
business being used as dictionary keys, or sort keys, or searched for,
or any of the other things we do with day-to-day values. That's not
something I really have an opinion on, yet.

I would not assert that NaN has no business being used here; again, my
alternative design accommodates all of these use cases.

Where we differ is that, in the case of a generic algorithm, my
alternative design would result in the author of that algorithm either
explicitly accommodating the presence of unordered values or asserting
their absence.

That is a nice property, except that IIUC you're talking about granting
an exception for the most common forms of algorithms.

Aside: I object to characterizing these things as unordered. For the
purposes of the algorithms that are written to accomodate them, they
must be very much ordered, if not with respect to each other, at *least*
with respect to other values in the space. In other words, there needs
to be an underlying strict-weak order or the algorithms won't work.

It is not an avoidable problem--this is the bump in the rug that
cannot be smoothed out.

I would posit that it is not possible to write an arbitrary generic
algorithm that (a) compares floating point values; (b) doesn't account for
NaN; and (c) behaves correctly, where correctly here means that it returns
what an average user would expect who is not thinking of floating point
comparison foibles.

Existence proof: any algorithm that, internally, uses binary search over
a sorted collection of Comparabble values, or stores Comparable values
in a tree-based dictionary, but does not care exactly how two distinct
values sort, will work just fine

...provided average users expect the existence of a distinct -0.0
;-)

For instance, generic `max` produces what to the average user is
nonsense if NaN compares greater than everything.

I am, however, concerned that ordinary valid computations can lead to
NaN and that allowing the appearance of a NaN to turn into a trap
much later in the program, where it is finally compared with
something, is not a behavior that would work for ordinary users.

That is my central worry with anything that turns operations on NaN into
a trap. I'd very much appreciate hearing your thoughts.

>> My purpose in exploring an alternative design is to see if it would be
>> > feasible for non-FP-aware comparison operators to refuse to compare
NaN,
>> > rather than giving different answers depending on context.
>>
>> So... to be clear, this is still different behavior based on context.
>> Is this not just as confusing a result?
>>
>> let nan = 0.0 / 0.0
>> print(nan == nan) // false
>> print([nan] == [nan]) // trap
>>
>> > I now strongly believe that this may make for a design simultaneously
>> > _less_ complex *and* _more_ comprehensive (as measured by the
>> > flatness-of-rug metric).
>>
>> I'm certainly willing to discuss it, but so far it doesn't seem like
>> you've been willing to answer the central questions above.
>>
>
> Clearly, I'm not understanding the central questions. Which ones have I
> left unanswered?

Again:

  Why is it the right behavior, for our audience, to trap when Equatable
  comparison happens to encounter NaN?

There are three possibilities (that I know of) when an equatable comparison
`==` encounters NaN:

* unspecified behavior (the current situation)
* a default behavior (as proposed by you and Ben, that would be ordering
NaN after all other values)
* trapping (as proposed by me)

I take it as given that you do not need to be convinced why unspecified
behavior is inferior to the alternatives. As to why trapping is superior to
a default behavior, I return to what I talk about above:

Rhetorical question--do you think that there is any design for Comparable
that would allow someone to compare floating point values *without* knowing
about the existence of NaN in such a way that an arbitrary generic
algorithm would behave as expected for a user who isn't thinking about
floating point comparison?

Yep, see above.

As I point out above, ordering NaN after all other values works for `sort`
but doesn't work so well for `max`. You can say that you'll provide a
special floating point `max`, but I can do you one better with a design
where the _generic algorithm_ and not the _comparable type_ sorts out what
happens with unordered values. In such a design, both generic `sort` and
generic `max` can offer fully specified, sensible behavior *without*
special versions for floating point.

Yeah... I'll believe it when I see it. The sensible specification, that
is ;-). So far what I've seen looks much too complicated for simple
notions like equality and ordering.

So, having said all of that, I return to address the question directly.
Let's consider where a user might encounter `==` unexpectedly trapping on
NaN:

* The user is writing FP code, intending to use FP comparisons, and hasn't
heard about the change in spelling. He or she is given immediate feedback
and uses the corresponding operators `&==`, etc. This is a one-time
learning experience.

Yeah, that happens as long as the appearance of NaN in his or her
computation is a case caught by tests. But I'm not confident it will
be.

* The user is authoring a generic algorithm and using `==`. Trapping is
optimal because either they will test their algorithm with floating point
NaN and then consider how to handle that special case, or they will not
test their algorithm and `==` is effectively a precondition that the
algorithm will not encounter NaN, which would be an untested scenario. If,
on the other hand, a default behavior is instead what occurs, it may not be
unspecified behavior to _you_ the author of this proposal for Comparable,
but it certainly would be behavior that has never been reasoned through by
the author of the generic algorithm.

* The user is calling a generic algorithm not designed for handling NaN
using a FP argument that is NaN. I believe that trapping is still the
correct behavior because silently proceeding with a result that the author
of the generic algorithm has never tested or thought about is potentially
more harmful than the user of the algorithm getting immediate feedback that
the algorithm has not been tested with NaN. For instance, how can I tell if
stdlib `max` is "NaN-ready"? Well, if `max` is not NaN-ready, in my
proposed design `max(.nan)` will trap right away; in yours, one must
inspect the result and consider whether the behavior is what is intended
and appropriate, which should principally be the job of the author of the
generic algorithm.

Again, I'm concerned that NaNs will arise as the result of computations
involving external inputs, and that programs that would otherwise
harmlessly propagate NaN values (just as non-signaling NaN was
designed!) will trap in the field, very far from the original source of
the problem, which means bugs will be very hard to correct.

--
-Dave