-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Provide heterogeneous floating-point comparisons, remove old compare hooks. #14987
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Provide heterogeneous floating-point comparisons, remove old compare hooks. #14987
Conversation
…e methods. This patch makes two fairly significant changes to the FloatingPoint and BinaryFloatingPoint protocols and concrete types. - Provides heterogeneous comparison between members conforming to BinaryFloatingPoint. This was part of the original SE-0067, but was not implementable at the time because we didn't have SE-0104 yet. - Makes the `isEqual(to:)`, `isLess(than:)` and `isLessThanOrEqualTo()` methods on FloatingPoint and concrete types obsolete. At the time that these were added, this was planned to be the means of conforming to Equatable / Comparable, but we ended up going in a different direction with the static operators ==, <, <= instead. Of particular relevance, the second change is breaking, I don't think that much code uses these methods directly, but if people have types that conform to FloatingPoint or BinaryFloatingPoint, they may have used these as the implementation hooks, as that was the original documented design. I'm not sure if we can actually make this change, and if we can I'm not precisely sure what affordances we need to make, so feedback on this point is particularly welcome.
Mmm. The For ordinary methods, if end users have already implemented an identically named method, that implementation correctly shadows any newly added standard library implementation. Overload resolution is different for operators, however. If end users have implemented a conflicting The As it happened, SwiftPM vended its own conditional But this is all to say that even if end users never directly call While implementing generic floating-point conversions, I considered enabling generic comparisons but did not (and I have the full, non-FIXME version ready to go that works with subnormals) because of an unresolved issue I'm very concerned about. There's a bug encountered on the integer side which makes heterogeneous comparison expose a potential footgun; @moiseev hacked around the problem in concrete code by putting in concrete implementations of homogeneous comparison operators in each type. The problem is much worse with floating-point code: If one operand is a literal, overload resolution will favor heterogeneous comparison with With integer literals, this now only shows up in generic code (where concrete overloads can't hack around the problem), thanks to @moiseev's hack, and now only becomes apparent when you write We still do not have a solution to this problem. The integer variation of the issue is tracked as SR-4984. In my book, the unintentional imprecision problem causes me to consider heterogeneous floating-point comparison to be "unimplementable" still. |
This doesn't actually seem to be the case:
Or am I misinterpreting your comment? |
Ah, I see, you mean in a generic context. Interesting. |
With concrete implementations present on the individual types, there's a hack that causes this footgun not to strike in concrete code. The same does not help in generic code. Try this on ToT today, then in your branch: func isOnePointTwo<T : BinaryFloatingPoint>(_ value: T) -> Bool {
return value == 1.2
}
isOnePointTwo(1.2 as Float80) Yeah, the result becomes different. Subtle, scary. |
Well, this just ended up pointing me to the fact that float literal parsing is fundamentally broken anyway. For whatever reason I hadn't noticed this previously, but the way
This is incorrect; the correct result would be Explanation: the literal is just slightly greater than Sigh. |
Sigh. Definitely not great, but at least that bug is messing up only ("only") a smaller set of values. I'm eager to hear about any progress as to the sorts of discussions @dabrahams mentioned regarding the interaction of literal parsing and operators. IMHO, if that can be resolved, then there's no impediment to this change here, but at the moment we're promoting these powerful generic facilities and then shipping subtle footguns. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @stephentyrone! A few edits and a Q for you.
/// // true | ||
/// x.isEqual(to: .nan) | ||
/// x == .nan |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just checking — with the heterogenous operators, do these expressions still type check properly? i.e. it correctly assumes Double
for the .nan
property?
@@ -1011,94 +1011,85 @@ public protocol FloatingPoint: SignedNumeric, Strideable, Hashable { | |||
/// - If `x` is `-greatestFiniteMagnitude`, then `x.nextDown` is `-infinity`. | |||
var nextDown: Self { get } | |||
|
|||
/// Returns a Boolean value indicating whether this instance is equal to the | |||
/// given value. | |||
/// Returns a Boolean value indicating whether `lhs` is equal to `rhs`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can all the abstracts follow this format:
Returns a Boolean value indicating whether (two values are equal / one value is less than another).
empty line
Start any discussion here...
/// - rhs: The second value to compare. | ||
/// - Returns: `true` if `lhs` and `rhs` represent the same extended real | ||
/// number value; otherwise, `false`. If either `lhs` or `rhs` is NaN, | ||
/// the result of this oeprator is `false`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oeprator
—> operator
(looks like this shows up four times)
/// - `-infinity` compares less than all values except for itself and NaN. | ||
/// - Every value except for NaN and `+infinity` compares less than | ||
/// `+infinity`. | ||
/// - Because NaN is incomparable with any value, this method returns `false` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of this method returns `false` when
let's say the result of this comparison is `false` when
, throughout.
Closing out this PR until we have a coherent plan for literals. |
This patch makes two fairly significant changes to the FloatingPoint and BinaryFloatingPoint protocols and concrete types.
Provides heterogeneous comparison between members conforming to BinaryFloatingPoint. This was part of the original SE-0067, but was not implementable at the time because we didn't have SE-0104 yet.
Makes the
isEqual(to:)
,isLess(than:)
andisLessThanOrEqualTo()
methods on FloatingPoint and concrete types obsolete. At the time that these were added, this was planned to be the means of conforming to Equatable / Comparable, but we ended up going in a different direction with the static operators ==, <, <= instead.Of particular relevance, the second change is breaking, I don't think that much code uses these methods directly, but if people have types that conform to FloatingPoint or BinaryFloatingPoint, they may have used these as the implementation hooks, as that was the original documented design. I'm not sure if we can actually make this change, and if we can I'm not precisely sure what affordances we need to make, so feedback on this point is particularly welcome.