[pitch] Comparison Reform

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.

-Joe

···

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.

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.

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.

A more comprehensive solution, with additional protocols or overloads, representation of unordered comparison etc, might be able to flatten the rug completely, but probably at the cost of introducing complexity that could act as a barrier to entry into the world of writing generic code.

···

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:

-Chris

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

I just wanted to ask for more detail on why this is a non-starter (it seems like most of my ideas are dismissed as “non-starters”, but I am rarely given a detailed reason why).

Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t be cosmetic. It would free us to use a non-optional Double, where we can guarantee the answer wouldn’t be NaN/nil. We would, as you say, have functions like ‘cos(Double?)->Double?’ which propagate the optional, but we could also add a ‘cos(Double)->Double’ overload which guarantees an actual result. For something like Tan, we would only have the optional version because the answer may actually be undefined, even when the input isn't.

Leave aside how one might implement such a system for a moment. The first major issue is that your idea does not address the issues we're talking about here:

We are debating, for instance, how to compare arrays with elements of type `Double`. In your design, the question remains how we would compare arrays with elements of type `Double?`. If the answer is that you cannot compare arrays of type `[Double?]`, then we have a problem, because that's the type that people will use when they ingest data that might contain NaN. Sure, they can unwrap each element before using their data, but they can also test each element with `isNaN` today. We are trying to *improve* on the user experience of comparing arrays of floating point values *without* checking if they contain NaN, not trying to take that feature away.

It solves the main problem we are dealing with in Comparable because defining Comparable on non-optional Double (without possibility of NaN) is fairly simple.

You could compare an array of ‘Double?' in the same way you could compare an array of ‘Int?’ now. It would work the same way optionals work everywhere else.

You would just test for a lack of numerical result by checking the optional in the usual Swift way: guard let, if let, etc...

Your design also doesn't address the problem of how NaN should compare with NaN. Only now, you've pushed the problem to `Optional`. By design, every `Optional<T>.none` compares equal to every other `Optional<U>.none` (yes, even of different types).

Yes, the one major difference in behavior, which I mentioned in my earlier email, is that we are replacing NaN != NaN with nil == nil. Everything else should behave the same. The part that saves us here, is that Swift forces you to explicitly consider the optional case.

Basically, I am taking the spirit/intent of the law over the letter of it. As far as I can tell, (quiet) NaN was originally designed to provide the functionality which we use optional for in Swift. The NaN != NaN thing came out of limitations of the computers in the 1980’s because you needed a way to tell if something was NaN or not (and isNaN() didn’t exist yet).

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. 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?'

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 haven’t been able to think of one. They are extremely similar, because they were designed for the same use-cases. The examples you have given so far (e.g. cos(Double)->Double) would all be trivial to migrate.

Thanks,
Jon

···

On Apr 25, 2017, at 5:25 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

In short, it would actually make people consider conditions which result in NaN because of Swift’s machinery which makes people consider nil.

It also allows non-optional Double to easily conform to Comparable, and removes the gotchas around Collections… Pretty big wins for a “cosmetic rename”. The only thing we lose is 'NaN != Nan' (because nil == nil), but then algorithms that had relied on that would be forced to consider the NaN/nil case explicitly because of the optionals.

It would also inter-op well with C/ObjC code by just having the compiler overlay Double? for Double…

Thanks,
Jon

On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The point is that, when you manipulate two real numbers, sometimes there is no numeric result. You cannot simply wish this away with a new numeric type because it is not an artifact of _modeling_ real numbers but rather intrinsic to mathematics itself.

I agree with the rest of what you said, but I have to disagree on this point. What I think he is saying is that, in Swift, we really should be representing the NaN case as an optional instead of a magic value on the type itself (similar to how swift uses an optional instead of NSNotFound).

In fact, that might be an actual option here. For ‘Double?’ the compiler could use the bit pattern for NaN internally to represent .none (I believe it does similar tricks to save space with other optional types). Then disallow reference to NaN within swift code. Functions or operations which could produce NaN would either have to produce an optional or trap in case of NaN. (e.g. the trig functions would likely return an optional, and 0/0 would trap).

I think it would actually lead to much better code because the compiler would force you to have to explicitly deal with the case of optional/NaN when it is possible. Migration would be tricky though...

This is essentially a cosmetic renaming from `Double` to `Double?`. There are rules for propagating NaN which numeric algorithms expect. For example, `cos(.nan)` returns a value. If your design is to work, every function that takes a `Double` will need to take a `Double?`.

Just as Swift String conforms to Unicode standards, FloatingPoint conforms to IEEE standards. You'd have to come up with enormous benefits to justify breaking that. Doing so for Swift 4 is plainly a non-starter.

Thanks,
Jon

Alternatively, we could introduce a new type `PotentiallyUnknown` that’s kind of like `Optional` but has different equality semantics. We’d probably also need to introduce a `PartiallyEquatable` (probably the wrong word) protocol too though. I don’t think this is really that feasible.

protocol PartiallyEquatable {
    static func ==(lhs: Self, rhs: Self) -> Bool?
}

enum PotentiallyUnknown<T: PartiallyEquatable> {
    case known(T)
    case unknown(T)
}

extension PotentiallyUnknown: PartiallyEquatable when T: PartiallyEquatable {
    static func ==(lhs: PotentiallyUnknown, rhs: PotentiallyUnknown) -> Bool? {
        guard case .known(let l) = lhs, case .known(let r) = rhs else {
            return nil
        }
        return l == r
    }
}

I think it’s a novel idea, but I don’t think it would work great in practice.

···

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

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

On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:
I just wanted to ask for more detail on why this is a non-starter (it seems like most of my ideas are dismissed as “non-starters”, but I am rarely given a detailed reason why).

Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t be cosmetic. It would free us to use a non-optional Double, where we can guarantee the answer wouldn’t be NaN/nil. We would, as you say, have functions like ‘cos(Double?)->Double?’ which propagate the optional, but we could also add a ‘cos(Double)->Double’ overload which guarantees an actual result. For something like Tan, we would only have the optional version because the answer may actually be undefined, even when the input isn't.

Leave aside how one might implement such a system for a moment. The first major issue is that your idea does not address the issues we're talking about here:

We are debating, for instance, how to compare arrays with elements of type `Double`. In your design, the question remains how we would compare arrays with elements of type `Double?`. If the answer is that you cannot compare arrays of type `[Double?]`, then we have a problem, because that's the type that people will use when they ingest data that might contain NaN. Sure, they can unwrap each element before using their data, but they can also test each element with `isNaN` today. We are trying to *improve* on the user experience of comparing arrays of floating point values *without* checking if they contain NaN, not trying to take that feature away.

It solves the main problem we are dealing with in Comparable because defining Comparable on non-optional Double (without possibility of NaN) is fairly simple.

You could compare an array of ‘Double?' in the same way you could compare an array of ‘Int?’ now. It would work the same way optionals work everywhere else.

You would just test for a lack of numerical result by checking the optional in the usual Swift way: guard let, if let, etc...

Your design also doesn't address the problem of how NaN should compare with NaN. Only now, you've pushed the problem to `Optional`. By design, every `Optional<T>.none` compares equal to every other `Optional<U>.none` (yes, even of different types).

Yes, the one major difference in behavior, which I mentioned in my earlier email, is that we are replacing NaN != NaN with nil == nil. Everything else should behave the same. The part that saves us here, is that Swift forces you to explicitly consider the optional case.

Basically, I am taking the spirit/intent of the law over the letter of it. As far as I can tell, (quiet) NaN was originally designed to provide the functionality which we use optional for in Swift. The NaN != NaN thing came out of limitations of the computers in the 1980’s because you needed a way to tell if something was NaN or not (and isNaN() didn’t exist yet).

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. 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?'

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 haven’t been able to think of one. They are extremely similar, because they were designed for the same use-cases. The examples you have given so far (e.g. cos(Double)->Double) would all be trivial to migrate.

Thanks,
Jon

In short, it would actually make people consider conditions which result in NaN because of Swift’s machinery which makes people consider nil.

It also allows non-optional Double to easily conform to Comparable, and removes the gotchas around Collections… Pretty big wins for a “cosmetic rename”. The only thing we lose is 'NaN != Nan' (because nil == nil), but then algorithms that had relied on that would be forced to consider the NaN/nil case explicitly because of the optionals.

It would also inter-op well with C/ObjC code by just having the compiler overlay Double? for Double…

Thanks,
Jon

On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The point is that, when you manipulate two real numbers, sometimes there is no numeric result. You cannot simply wish this away with a new numeric type because it is not an artifact of _modeling_ real numbers but rather intrinsic to mathematics itself.

I agree with the rest of what you said, but I have to disagree on this point. What I think he is saying is that, in Swift, we really should be representing the NaN case as an optional instead of a magic value on the type itself (similar to how swift uses an optional instead of NSNotFound).

In fact, that might be an actual option here. For ‘Double?’ the compiler could use the bit pattern for NaN internally to represent .none (I believe it does similar tricks to save space with other optional types). Then disallow reference to NaN within swift code. Functions or operations which could produce NaN would either have to produce an optional or trap in case of NaN. (e.g. the trig functions would likely return an optional, and 0/0 would trap).

I think it would actually lead to much better code because the compiler would force you to have to explicitly deal with the case of optional/NaN when it is possible. Migration would be tricky though...

This is essentially a cosmetic renaming from `Double` to `Double?`. There are rules for propagating NaN which numeric algorithms expect. For example, `cos(.nan)` returns a value. If your design is to work, every function that takes a `Double` will need to take a `Double?`.

Just as Swift String conforms to Unicode standards, FloatingPoint conforms to IEEE standards. You'd have to come up with enormous benefits to justify breaking that. Doing so for Swift 4 is plainly a non-starter.

Thanks,
Jon

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

I just wanted to ask for more detail on why this is a non-starter (it
seems like most of my ideas are dismissed as “non-starters”, but I am
rarely given a detailed reason why).

Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t
be cosmetic. It would free us to use a non-optional Double, where we can
guarantee the answer wouldn’t be NaN/nil. We would, as you say, have
functions like ‘cos(Double?)->Double?’ which propagate the optional, but we
could also add a ‘cos(Double)->Double’ overload which guarantees an actual
result. For something like Tan, we would only have the optional version
because the answer may actually be undefined, even when the input isn't.

Leave aside how one might implement such a system for a moment. The first
major issue is that your idea does not address the issues we're talking
about here:

We are debating, for instance, how to compare arrays with elements of type
`Double`. In your design, the question remains how we would compare arrays
with elements of type `Double?`. If the answer is that you cannot compare
arrays of type `[Double?]`, then we have a problem, because that's the type
that people will use when they ingest data that might contain NaN. Sure,
they can unwrap each element before using their data, but they can also
test each element with `isNaN` today. We are trying to *improve* on the
user experience of comparing arrays of floating point values *without*
checking if they contain NaN, not trying to take that feature away.

It solves the main problem we are dealing with in Comparable because
defining Comparable on non-optional Double (without possibility of NaN) is
fairly simple.

Yes, but `Double` would not the currency type for floating point work.
Since any input might contain NaN, people will be working pervasively with
`Double?`. Some consideration as to how the type would have to be designed
(I will leave that as an exercise to the reader) would reveal that `Double`
would either have trapping arithmetic operators or no arithmetic operators
at all. Who would use such a type? And for what purpose? No, you've just
pushed the problem from Double to Double?, but the issue is unsolved, and
now we have two types where before we had one.

You could compare an array of ‘Double?' in the same way you could compare
an array of ‘Int?’ now. It would work the same way optionals work
everywhere else.

Right, but again, `Double?` would be the currency type for floating point
work. And you have still not told me how arrays of `Double?` elements would
work. Would two arrays of NaN compare equal? Would two NaNs compare equal?
If NaN == NaN, then we've got a problem. The starting point for this
discussion is that, when working with floating point types, NaN != NaN.
It's required by IEEE standards, and it's relied upon by users.

You would just test for a lack of numerical result by checking the optional

in the usual Swift way: guard let, if let, etc...

Your design also doesn't address the problem of how NaN should compare
with NaN. Only now, you've pushed the problem to `Optional`. By design,
every `Optional<T>.none` compares equal to every other `Optional<U>.none`
(yes, even of different types).

Yes, the one major difference in behavior, which I mentioned in my earlier
email, is that we are replacing NaN != NaN with nil == nil. Everything
else should behave the same. The part that saves us here, is that Swift
forces you to explicitly consider the optional case.

Basically, I am taking the spirit/intent of the law over the letter of
it. As far as I can tell, (quiet) NaN was originally designed to provide
the functionality which we use optional for in Swift. The NaN != NaN thing
came out of limitations of the computers in the 1980’s because you needed a
way to tell if something was NaN or not (and isNaN() didn’t exist yet).

I'll refer you to Steve Canon's answer on StackOverflow:

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.

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.

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.

I haven’t been able to think of one. They are extremely similar, because

···

On Tue, Apr 25, 2017 at 8:38 PM, Jonathan Hull <jhull@gbis.com> wrote:

On Apr 25, 2017, at 5:25 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull@gbis.com> wrote:
they were designed for the same use-cases. The examples you have given so
far (e.g. cos(Double)->Double) would all be trivial to migrate.

Thanks,
Jon

In short, it would actually make people consider conditions which result

in NaN because of Swift’s machinery which makes people consider nil.

It also allows non-optional Double to easily conform to Comparable, and
removes the gotchas around Collections… Pretty big wins for a “cosmetic
rename”. The only thing we lose is 'NaN != Nan' (because nil == nil), but
then algorithms that had relied on that would be forced to consider the
NaN/nil case explicitly because of the optionals.

It would also inter-op well with C/ObjC code by just having the compiler
overlay Double? for Double…

Thanks,
Jon

On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull@gbis.com> wrote:

On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution < >>> swift-evolution@swift.org> wrote:

The point is that, when you manipulate two real numbers, sometimes there
is no numeric result. You cannot simply wish this away with a new numeric
type because it is not an artifact of _modeling_ real numbers but rather
intrinsic to mathematics itself.

I agree with the rest of what you said, but I have to disagree on this
point. What I think he is saying is that, in Swift, we really should be
representing the NaN case as an optional instead of a magic value on the
type itself (similar to how swift uses an optional instead of NSNotFound).

In fact, that might be an actual option here. For ‘Double?’ the
compiler could use the bit pattern for NaN internally to represent .none (I
believe it does similar tricks to save space with other optional types).
Then disallow reference to NaN within swift code. Functions or operations
which could produce NaN would either have to produce an optional or trap in
case of NaN. (e.g. the trig functions would likely return an optional, and
0/0 would trap).

I think it would actually lead to much better code because the compiler
would force you to have to explicitly deal with the case of optional/NaN
when it is possible. Migration would be tricky though...

This is essentially a cosmetic renaming from `Double` to `Double?`. There
are rules for propagating NaN which numeric algorithms expect. For example,
`cos(.nan)` returns a value. If your design is to work, every function that
takes a `Double` will need to take a `Double?`.

Just as Swift String conforms to Unicode standards, FloatingPoint
conforms to IEEE standards. You'd have to come up with enormous benefits to
justify breaking that. Doing so for Swift 4 is plainly a non-starter.

Thanks,

Jon

Seems to me that the core of the issue is that the IEEE spec enforces the observation that `==` is usually the wrong question to ask from a mathematical PoV when it comes to infinities and NaNs, but we don’t have anything better to put in its place when we want to ask it anyway from a less rigorous PoV.

What about having `==` return the layman’s answers, and `===` return IEEE's answer? That way the behavior is the same regardless of generic vs concrete. Or the other way around, since `===` means “equivalent” / “identical” (I forget which), which at least to me implies a stronger similarity than just “equals" (plus, this way wouldn’t be source-breaking). Either way, either behavior is readily available. And either way, since none of the stdlib numeric types have reference semantics, we can have them all follow the same pattern WRT “==“ and “===“. For the integer types, the two operators would be interchangeable, but it’d allow for generic numeric code that could rely on both operators being present.

- Dave Sweeris

···

On Apr 18, 2017, at 8: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 <mailto:swift-evolution@swift.org>> wrote:

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

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto: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.

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.

A more comprehensive solution, with additional protocols or overloads, representation of unordered comparison etc, might be able to flatten the rug completely, but probably at the cost of introducing complexity that could act as a barrier to entry into the world of writing generic code.

"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.

Gwendal Roué

···

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.

For what it’s worth, I agree with Ben and Joe; there are some surprises lurking here, but they are less hazardous to the inexperienced programmer than the surprises that lurk with any other solution I’ve seen.

– Steve

···

On Apr 18, 2017, at 11: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 <mailto:swift-evolution@swift.org>> wrote:

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

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto: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.

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.

A more comprehensive solution, with additional protocols or overloads, representation of unordered comparison etc, might be able to flatten the rug completely, but probably at the cost of introducing complexity that could act as a barrier to entry into the world of writing generic code.

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? 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.

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. 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.

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. 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).

A more comprehensive solution, with additional protocols or overloads,

···

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:
representation of unordered comparison etc, might be able to flatten the
rug completely, but probably at the cost of introducing complexity that
could act as a barrier to entry into the world of writing generic code.

-Chris

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

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

I hadn't had time to read this before, but now my inbox is getting too
big and I am forced to deal with the backlog. Just wanted to say, this
is a great response, Ben. Bravo!

···

on Tue Apr 18 2017, Ben Cohen <ben_cohen-AT-apple.com> 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.

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.

A more comprehensive solution, with additional protocols or
overloads, representation of unordered comparison etc, might be able
to flatten the rug completely, but probably at the cost of introducing
complexity that could act as a barrier to entry into the world of
writing generic code.

--
-Dave

I just wanted to ask for more detail on why this is a non-starter (it seems like most of my ideas are dismissed as “non-starters”, but I am rarely given a detailed reason why).

Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t be cosmetic. It would free us to use a non-optional Double, where we can guarantee the answer wouldn’t be NaN/nil. We would, as you say, have functions like ‘cos(Double?)->Double?’ which propagate the optional, but we could also add a ‘cos(Double)->Double’ overload which guarantees an actual result. For something like Tan, we would only have the optional version because the answer may actually be undefined, even when the input isn't.

Leave aside how one might implement such a system for a moment. The first major issue is that your idea does not address the issues we're talking about here:

We are debating, for instance, how to compare arrays with elements of type `Double`. In your design, the question remains how we would compare arrays with elements of type `Double?`. If the answer is that you cannot compare arrays of type `[Double?]`, then we have a problem, because that's the type that people will use when they ingest data that might contain NaN. Sure, they can unwrap each element before using their data, but they can also test each element with `isNaN` today. We are trying to *improve* on the user experience of comparing arrays of floating point values *without* checking if they contain NaN, not trying to take that feature away.

It solves the main problem we are dealing with in Comparable because defining Comparable on non-optional Double (without possibility of NaN) is fairly simple.

Yes, but `Double` would not the currency type for floating point work. Since any input might contain NaN, people will be working pervasively with `Double?`. Some consideration as to how the type would have to be designed (I will leave that as an exercise to the reader) would reveal that `Double` would either have trapping arithmetic operators or no arithmetic operators at all. Who would use such a type? And for what purpose? No, you've just pushed the problem from Double to Double?, but the issue is unsolved, and now we have two types where before we had one.

Any value of type ‘Double’ would be guaranteed not to contain NaN. Some things coming in from ObjC land might use ‘Double?', but that is only a single ‘guard let' statement away from ‘Double’. Once you have a Double, you would in most cases get a Double back out. In the event that you do receive a ‘Double?’, you know that you actually do have to deal with the optional case (either through chaining or bailing early in the case of nil). It enforces proper behavior which would likely be implemented in a buggy way under the current system.

You would probably have trapping arithmetic for divide by zero, just like Int. The other option is to return an optional, but you and Dave have previously made strong arguments for trapping.

You could compare an array of ‘Double?' in the same way you could compare an array of ‘Int?’ now. It would work the same way optionals work everywhere else.

Right, but again, `Double?` would be the currency type for floating point work. And you have still not told me how arrays of `Double?` elements would work. Would two arrays of NaN compare equal? Would two NaNs compare equal? If NaN == NaN, then we've got a problem. The starting point for this discussion is that, when working with floating point types, NaN != NaN. It's required by IEEE standards, and it's relied upon by users.

‘Double?' would only be the currency type for interfacing with external (non-swift) code. Most swift code would still just use Double… and when it requires ‘Double?’, you know you need to deal with the possibility of nil. Right now, NaN is just ignored for the most part… even in some standard library code.

You would just test for a lack of numerical result by checking the optional in the usual Swift way: guard let, if let, etc...

Your design also doesn't address the problem of how NaN should compare with NaN. Only now, you've pushed the problem to `Optional`. By design, every `Optional<T>.none` compares equal to every other `Optional<U>.none` (yes, even of different types).

Yes, the one major difference in behavior, which I mentioned in my earlier email, is that we are replacing NaN != NaN with nil == nil. Everything else should behave the same. The part that saves us here, is that Swift forces you to explicitly consider the optional case.

Basically, I am taking the spirit/intent of the law over the letter of it. As far as I can tell, (quiet) NaN was originally designed to provide the functionality which we use optional for in Swift. The NaN != NaN thing came out of limitations of the computers in the 1980’s because you needed a way to tell if something was NaN or not (and isNaN() didn’t exist yet).

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.

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...

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 still haven’t heard any actual working use cases where this property is actually important other than in legacy C code.

···

On Apr 25, 2017, at 7:17 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:
On Tue, Apr 25, 2017 at 8:38 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 25, 2017, at 5:25 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

I haven’t been able to think of one. They are extremely similar, because they were designed for the same use-cases. The examples you have given so far (e.g. cos(Double)->Double) would all be trivial to migrate.

Thanks,
Jon

In short, it would actually make people consider conditions which result in NaN because of Swift’s machinery which makes people consider nil.

It also allows non-optional Double to easily conform to Comparable, and removes the gotchas around Collections… Pretty big wins for a “cosmetic rename”. The only thing we lose is 'NaN != Nan' (because nil == nil), but then algorithms that had relied on that would be forced to consider the NaN/nil case explicitly because of the optionals.

It would also inter-op well with C/ObjC code by just having the compiler overlay Double? for Double…

Thanks,
Jon

On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The point is that, when you manipulate two real numbers, sometimes there is no numeric result. You cannot simply wish this away with a new numeric type because it is not an artifact of _modeling_ real numbers but rather intrinsic to mathematics itself.

I agree with the rest of what you said, but I have to disagree on this point. What I think he is saying is that, in Swift, we really should be representing the NaN case as an optional instead of a magic value on the type itself (similar to how swift uses an optional instead of NSNotFound).

In fact, that might be an actual option here. For ‘Double?’ the compiler could use the bit pattern for NaN internally to represent .none (I believe it does similar tricks to save space with other optional types). Then disallow reference to NaN within swift code. Functions or operations which could produce NaN would either have to produce an optional or trap in case of NaN. (e.g. the trig functions would likely return an optional, and 0/0 would trap).

I think it would actually lead to much better code because the compiler would force you to have to explicitly deal with the case of optional/NaN when it is possible. Migration would be tricky though...

This is essentially a cosmetic renaming from `Double` to `Double?`. There are rules for propagating NaN which numeric algorithms expect. For example, `cos(.nan)` returns a value. If your design is to work, every function that takes a `Double` will need to take a `Double?`.

Just as Swift String conforms to Unicode standards, FloatingPoint conforms to IEEE standards. You'd have to come up with enormous benefits to justify breaking that. Doing so for Swift 4 is plainly a non-starter.

Thanks,
Jon

I just wanted to ask for more detail on why this is a non-starter (it seems like most of my ideas are dismissed as “non-starters”, but I am rarely given a detailed reason why).

Migration would be a renaming from ‘Double' to ‘Double?’, but it wouldn’t be cosmetic. It would free us to use a non-optional Double, where we can guarantee the answer wouldn’t be NaN/nil. We would, as you say, have functions like ‘cos(Double?)->Double?’ which propagate the optional, but we could also add a ‘cos(Double)->Double’ overload which guarantees an actual result. For something like Tan, we would only have the optional version because the answer may actually be undefined, even when the input isn't.

Leave aside how one might implement such a system for a moment. The first major issue is that your idea does not address the issues we're talking about here:

We are debating, for instance, how to compare arrays with elements of type `Double`. In your design, the question remains how we would compare arrays with elements of type `Double?`. If the answer is that you cannot compare arrays of type `[Double?]`, then we have a problem, because that's the type that people will use when they ingest data that might contain NaN. Sure, they can unwrap each element before using their data, but they can also test each element with `isNaN` today. We are trying to *improve* on the user experience of comparing arrays of floating point values *without* checking if they contain NaN, not trying to take that feature away.

It solves the main problem we are dealing with in Comparable because defining Comparable on non-optional Double (without possibility of NaN) is fairly simple.

Yes, but `Double` would not the currency type for floating point work. Since any input might contain NaN, people will be working pervasively with `Double?`. Some consideration as to how the type would have to be designed (I will leave that as an exercise to the reader) would reveal that `Double` would either have trapping arithmetic operators or no arithmetic operators at all. Who would use such a type? And for what purpose? No, you've just pushed the problem from Double to Double?, but the issue is unsolved, and now we have two types where before we had one.

Any value of type ‘Double’ would be guaranteed not to contain NaN. Some things coming in from ObjC land might use ‘Double?', but that is only a single ‘guard let' statement away from ‘Double’. Once you have a Double, you would in most cases get a Double back out. In the event that you do receive a ‘Double?’, you know that you actually do have to deal with the optional case (either through chaining or bailing early in the case of nil). It enforces proper behavior which would likely be implemented in a buggy way under the current system.

You would probably have trapping arithmetic for divide by zero, just like Int. The other option is to return an optional, but you and Dave have previously made strong arguments for trapping.

You could compare an array of ‘Double?' in the same way you could compare an array of ‘Int?’ now. It would work the same way optionals work everywhere else.

Right, but again, `Double?` would be the currency type for floating point work. And you have still not told me how arrays of `Double?` elements would work. Would two arrays of NaN compare equal? Would two NaNs compare equal? If NaN == NaN, then we've got a problem. The starting point for this discussion is that, when working with floating point types, NaN != NaN. It's required by IEEE standards, and it's relied upon by users.

‘Double?' would only be the currency type for interfacing with external (non-swift) code. Most swift code would still just use Double… and when it requires ‘Double?’, you know you need to deal with the possibility of nil. Right now, NaN is just ignored for the most part… even in some standard library code.

You would just test for a lack of numerical result by checking the optional in the usual Swift way: guard let, if let, etc...

Your design also doesn't address the problem of how NaN should compare with NaN. Only now, you've pushed the problem to `Optional`. By design, every `Optional<T>.none` compares equal to every other `Optional<U>.none` (yes, even of different types).

Yes, the one major difference in behavior, which I mentioned in my earlier email, is that we are replacing NaN != NaN with nil == nil. Everything else should behave the same. The part that saves us here, is that Swift forces you to explicitly consider the optional case.

Basically, I am taking the spirit/intent of the law over the letter of it. As far as I can tell, (quiet) NaN was originally designed to provide the functionality which we use optional for in Swift. The NaN != NaN thing came out of limitations of the computers in the 1980’s because you needed a way to tell if something was NaN or not (and isNaN() didn’t exist yet).

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.

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.

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?

···

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 <mailto:xiaodi.wu@gmail.com>> wrote:
On Tue, Apr 25, 2017 at 8:38 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 25, 2017, at 5:25 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Tue, Apr 25, 2017 at 6:53 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

I still haven’t heard any actual working use cases where this property is actually important other than in legacy C code.

I haven’t been able to think of one. They are extremely similar, because they were designed for the same use-cases. The examples you have given so far (e.g. cos(Double)->Double) would all be trivial to migrate.

Thanks,
Jon

In short, it would actually make people consider conditions which result in NaN because of Swift’s machinery which makes people consider nil.

It also allows non-optional Double to easily conform to Comparable, and removes the gotchas around Collections… Pretty big wins for a “cosmetic rename”. The only thing we lose is 'NaN != Nan' (because nil == nil), but then algorithms that had relied on that would be forced to consider the NaN/nil case explicitly because of the optionals.

It would also inter-op well with C/ObjC code by just having the compiler overlay Double? for Double…

Thanks,
Jon

On Apr 16, 2017, at 11:44 AM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:

On Sun, Apr 16, 2017 at 1:14 PM, Jonathan Hull <jhull@gbis.com <mailto:jhull@gbis.com>> wrote:

On Apr 16, 2017, at 10:42 AM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

The point is that, when you manipulate two real numbers, sometimes there is no numeric result. You cannot simply wish this away with a new numeric type because it is not an artifact of _modeling_ real numbers but rather intrinsic to mathematics itself.

I agree with the rest of what you said, but I have to disagree on this point. What I think he is saying is that, in Swift, we really should be representing the NaN case as an optional instead of a magic value on the type itself (similar to how swift uses an optional instead of NSNotFound).

In fact, that might be an actual option here. For ‘Double?’ the compiler could use the bit pattern for NaN internally to represent .none (I believe it does similar tricks to save space with other optional types). Then disallow reference to NaN within swift code. Functions or operations which could produce NaN would either have to produce an optional or trap in case of NaN. (e.g. the trig functions would likely return an optional, and 0/0 would trap).

I think it would actually lead to much better code because the compiler would force you to have to explicitly deal with the case of optional/NaN when it is possible. Migration would be tricky though...

This is essentially a cosmetic renaming from `Double` to `Double?`. There are rules for propagating NaN which numeric algorithms expect. For example, `cos(.nan)` returns a value. If your design is to work, every function that takes a `Double` will need to take a `Double?`.

Just as Swift String conforms to Unicode standards, FloatingPoint conforms to IEEE standards. You'd have to come up with enormous benefits to justify breaking that. Doing so for Swift 4 is plainly a non-starter.

Thanks,
Jon

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

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.

-Joe

···

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.

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 should have been more clear, my apologies. When you write:

I also doubt that this will cause problems in practice.

It's normal to wonder if this is true. Bugs created by equality inconsistencies would be so hard to debug! So few people will even consider the eventually of different equalities.

You're almost on the verge on saying "this is not a bug, this is a feature". But I'm suspicious of features that are that much unexpected. Many unique Swift features were abundantly commented and evangelized, and made their way in the Swift culture. Do you think this could happen for your suggestion?

Gwendal

Agreed.

John.

···

On Apr 18, 2017, at 11:43 AM, Stephen Canon via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 18, 2017, at 11:40 AM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto: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.

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.

A more comprehensive solution, with additional protocols or overloads, representation of unordered comparison etc, might be able to flatten the rug completely, but probably at the cost of introducing complexity that could act as a barrier to entry into the world of writing generic code.

For what it’s worth, I agree with Ben and Joe; there are some surprises lurking here, but they are less hazardous to the inexperienced programmer than the surprises that lurk with any other solution I’ve seen.

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

Gwendal

···

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.

If Xiaodi is correct and we can actually fit floats to standard-library Comparable and Equatable without unacceptable tradeoffs (again, not an FP expert, can’t judge how often NaNs actually come up), that’s obviously optimal. If we can’t, I think we need to keep the standard library clear and give FloatingPoints two implementations of Comparable - one according to IEEE754, which can be the default conformance, and another which conforms to the standard library semantics of what “Comparable” and “Equatable” operators should do.

If this was any other similar problem, I couldn’t think of a better solution. Zero run-time cost, clear semantics which prevent unexpected behaviour. Unlike the contextual approach, which chooses for you some semantics which you may not want, you would always have the option of explicitly saying which type of comparison you want for FloatingPoints.

Users will often expect a Float to compare to another Float the same way an Int compares to another Int. The truth is that floating-points just aren’t trivially comparable or equatable, because of things like .NaN never equalling another .NaN. Again, not an expert, but my intuition is that rather than try to hide the complexity, or wish it away by trapping, maybe we should embrace it and just provide a good API to work with multiple interpretations of Comparable/Equatable when it comes to FloatingPoints.

- Karl

···

On 18 Apr 2017, at 19:06, David Sweeris via swift-evolution <swift-evolution@swift.org> wrote:

On Apr 18, 2017, at 8:40 AM, Ben Cohen via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Apr 17, 2017, at 9:40 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

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

On Apr 15, 2017, at 9:49 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org <mailto: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.

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.

A more comprehensive solution, with additional protocols or overloads, representation of unordered comparison etc, might be able to flatten the rug completely, but probably at the cost of introducing complexity that could act as a barrier to entry into the world of writing generic code.

Seems to me that the core of the issue is that the IEEE spec enforces the observation that `==` is usually the wrong question to ask from a mathematical PoV when it comes to infinities and NaNs, but we don’t have anything better to put in its place when we want to ask it anyway from a less rigorous PoV.

What about having `==` return the layman’s answers, and `===` return IEEE's answer? That way the behavior is the same regardless of generic vs concrete. Or the other way around, since `===` means “equivalent” / “identical” (I forget which), which at least to me implies a stronger similarity than just “equals" (plus, this way wouldn’t be source-breaking). Either way, either behavior is readily available. And either way, since none of the stdlib numeric types have reference semantics, we can have them all follow the same pattern WRT “==“ and “===“. For the integer types, the two operators would be interchangeable, but it’d allow for generic numeric code that could rely on both operators being present.

- Dave Sweeris
_______________________________________________
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

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.

···

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

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).

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.

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?).

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 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…

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

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

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

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

···

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 <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:

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

···

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" :-)