> On Oct 21, 2017, at 3:02 PM, Xiaodi Wu <xiaodi...@gmail.com> wrote: > > On Fri, Oct 20, 2017 at 2:42 PM, Stephen Canon <sca...@apple.com > <mailto:sca...@apple.com>> wrote: >> On Oct 20, 2017, at 8:21 AM, David Zarzycki via swift-dev >> <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote: >> >>> On Oct 20, 2017, at 07:51, Xiaodi Wu via swift-dev <swift-dev@swift.org >>> <mailto:swift-dev@swift.org>> wrote: >>> >>> On Fri, Oct 20, 2017 at 1:22 AM, Jonathan Hull <jh...@gbis.com >>> <mailto:jh...@gbis.com>> wrote: >>> +1 for trapping unless using &==. In the case of ‘Float?’ we could also >>> map to nil. >>> >>> This is probably a more appropriate discussion for evolution though... >>> >>> >>>> On Oct 19, 2017, at 9:48 PM, Brent Royal-Gordon via swift-dev >>>> <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote: >>>> >>>>> On Oct 19, 2017, at 4:29 PM, Xiaodi Wu via swift-dev <swift-dev@swift.org >>>>> <mailto:swift-dev@swift.org>> wrote: >>>>> >>>>> D) Must floating-point IEEE-compliant equivalence be spelled `==`? >>>>> >>>>> In my view, this is something open for debate. I see no reason why it >>>>> cannot be migrated to `&==` if it were felt that `==` *must* be a full >>>>> equivalence relation. I believe this is controversial, however. >>>> >>>> I actually got partway through writing up a pitch on this yesterday, but >>>> my opinion is that NaNs are so exceptional, and so prone to misuse, that >>>> we ought to treat them like integer arithmetic overflows: trap when >>>> they're detected, unless you use an `&` variant operator which indicates >>>> you know what you're doing. >>>> >>>> I strongly suspect that, in practice, most float-manipulating code is not >>>> prepared to handle NaN and will not do anything sensible in its presence. >>>> For example, Apple platforms use floating-point types for geometry, color >>>> components, GPS locations, etc. Very little of this code will do anything >>>> sensible in the presence of a NaN. Arguably, it'd be better to exclude >>>> them through the type system, but I don't think that's a realistic >>>> possibility—we would need to have done that in a more >>>> source-break-friendly era. But that doesn't have to mean we're completely >>>> stuck. >>> >>> >>> Built-in floating point operators, as well as libc/libm math functions, are >>> designed to propagate NaN correctly. This is not meant to be a thread about >>> NaN, and we need to be cautious to define the scope of the problem to be >>> solved from the outset. The tendency of having ever-expanding discussion >>> where issues such as method names turn into discussions about the entire >>> standard library go nowhere. >>> >>> The question here is about `==` specifically and how to accommodate partial >>> equivalence relations. For sanity, we start with the premise that NaN will >>> forever be as it is. >> >> I support Jonathan’s argument. If Swift wants to trap on NaN to improve >> self-consistency and simplicity, then the tradeoff might be worth it. The >> alternative, teaching the Equality protocol about NaNs, feels like “the tail >> wagging the dog". >> >> In short: what IEEE requires of floating-point hardware is separable from >> IEEE’s opinions about language/library design. > > Just to be precise: IEEE 754 places no requirements on hardware. The entirety > of IEEE 754 is about what *languages* should provide. It just happens to be > advantageous to implement many of the requirements directly in hardware. > > [The rest of this is a response to the thread as a whole, not to Dave] > > I have no philosophical objection to trapping on NaN. IEEE 754 says that the > default behavior should be to not trap, but other non-default forms of > exception handling* are explicitly allowed by IEEE 754. > > From a practical standpoint, it’s is counter to everything about the way much > floating-point hardware is designed, and that should give us some pause. On > x86 it’s possible to unmask the “invalid floating point exception”, which > results in any operation that generates a NaN faulting. However, it will > *not* cause a fault if an operand is already a quiet NaN, so Swift would need > to either test every operand that’s read from memory at the time that it’s > moved into register, or test every result. > > On some non-x86 architectures (including in particular most ARM > implementations) there is no hardware support for unmasking exceptions, so > there’s no way to automatically trap on invalid operations, you would have to > explicitly check for NaN on every operation. This is much, much more > expensive than checking for overflow on integer arithmetic (where for > addition / subtraction, it’s just an easily-predicted conditional branch). > Including these checks would introduce significant code bloat and slow down > naive arithmetic by roughly an order of magnitude on current hardware, which > is probably a non-starter. > > Trapping only for == is much, much more palatable, but as Xiaodi said, > doesn’t actually get you the semantics that you want for ==. > > &== is ugly but workable. You will have inevitable bugs from people who > naively adapt code from literally any other language that assumes IEEE 754 > semantics for ==, however. > > – Steve > > [*] note that “exception handling” in an IEEE 754 context does not mean what > you think it does if you’re coming from a generic CS not-floating-point > background. > > The performance aspect of this issue are daunting, and I'm glad you brought > it up. On a cursory reading, having every NaN value compare equal to every > other NaN value is likely to be several times, if not orders of magnitude, > slower than the hardware IEEE implementation. This would be an extraordinary > cost and makes me wonder if this is at all a good idea.
One counter argument is that &== will retain full speed where important (e.g. in a tight loop). Off the top of my head, a naive implementation of == for Float (where Nan == Nan) would be: func == (a: Float, b: Float)->Bool { return (a &== b) || !(a &== a) || !(b &== b) } The good news is that ‘a' and ‘b' only need to be loaded into registers once. Also, it could short circuit if 'a &== b' is true (if that is faster). We could probably do better with a little cleverness. > It would serve us well to re-evaluate what generic algorithms absolutely > require a full equivalence relation. Let's take a look at `Array`, for > example. > > - `index(of:)` works perfectly sensibly without such a relation; if no NaN is > equal to any other NaN, `index(of: .nan)` is appropriately `nil`. > - `min()` and `max()` are something else entirely, as we're talking here only > of equivalence and not of total order, which is another issue altogether. > - `sort()` is problematic, but not if a custom predicate is supplied. > - `split()` only runs into problems if specifically trying to split a > sequence on `.nan`, but again this would be unsurprising if no NaN is equal > to any other NaN. > - `==` is broken but can be fixed as shown in PR #12503. > My main worry would be generic algorithms on something one level of generalization away. For example, how should a Set handle double insertion of a struct which has a Float in it. It is very likely that the creator of that struct just && together == of the elements, so the set will end up with two identical entries (neither of which would register as being a member when the set is asked). I am currently leaning towards NaN == NaN behavior, with a warning explaining &== when used with Float == Float (and a way to silence it). That way, you at least have progressive disclosure. Thanks, Jon
_______________________________________________ swift-dev mailing list swift-dev@swift.org https://lists.swift.org/mailman/listinfo/swift-dev