[swift-evolution] [Proposal draft] Enhanced floating-point protocols

Dave Abrahams dabrahams at apple.com
Mon Apr 18 17:12:08 CDT 2016

on Fri Apr 15 2016, Brent Royal-Gordon <swift-evolution at swift.org> wrote:

>>> Are there potential conforming types which aren't Comparable?
>> Not at present, but I expect there to be in the future.  Modular integers and complex numbers come to mind as the most obvious examples.
> Ooh, those are great examples. That is definitely the right decision, then.
> (One thing I've been vaguely concerned with is dimensional analysis. A
> strong type system opens up the possibility of marking numbers with
> units, or at least dimensions, so that the type system can catch when
> you try to pass a Second<Pound<T>> to a call that's expecting a
> Second<Newton<T>>. Doing this properly requires more freedom in the
> multiplication and division operators' parameters and return
> values—but of course, it also requires higher-kinded types to
> construct the right return values, so it's out of reach for Swift 3
> anyway.)
>>>> /// NaN `payloads`.  `FloatingPoint` types should either treat inadmissible
>>>> /// payloads as zero, or mask them to create an admissible payload.
>>>> static func nan(payload payload: RawSignificand, signaling: Bool) -> Self
>>> This seems unusually tolerant of bad inputs. Should this instead be
>>> a precondition, and have an (elidable in unchecked mode) trap if
>>> it's violated?
>> I don’t think that it’s a particularly useful error to detect,
> Maybe it's just my lack of knowledge, but I feel like, if you are
> calling this method, you probably care about getting that payload into
> the resulting NaN. Otherwise you would be using the `nan`
> property. (There is no `signalingNan` property, but there could be.)
> What do people use NaN payloads for? Are there a lot of cases where
> the payload is a nice-to-have, but it's okay to destroy or even mangle
> it?

Another option: trap the precondition violation only in debug builds.

>> and different floating point types may differ greatly in what
>> payloads they support (if any), because they might choose to reserve
>> those encodings for other purposes.
> If the data you can actually get into a NaN varies so much from one
> type to another, is this API a useful one to offer on the protocol? Is
> it something you can reliably use on "any floating-point type" without
> knowing which type it is?
> (It also now occurs to me that this might be better off as an initializer: `init(nanWithPayload:signaling:)`.)
>>> Reading these, I find the use of "least" a little bit misleading—it seems like they should be negative.
>> Magnitudes are strictly positive.  In fairness, that may not be immediately obvious to all readers.
> It does make sense now that you've said so! It might be a good doc comment note, though.
>>> 	static var positiveNormals: ClosedRange<Self> { get }
>>> 	static var positiveSubnormals: ClosedRange<Self> { get }
>>> 	Double.positiveNormals.upperBound		// DBL_MAX
>>> 	Double.positiveNormals.lowerBound		// DBL_MIN
>>> 	Double.positiveSubnormals.upperBound	// Self.positiveNormals.lowerBound.nextDown
>>> 	Double.positiveSubnormals.lowerBound	// 0.nextUp
>> This seems wildly over-engineered to me personally.  Every language
>> I surveyed provides (a subset of) these quantities as simple scalar
>> values.
> Well, I am rather prone to wild over-engineering. :^)
> One place where a range like FloatingPoint.positives would be useful is in random number generation. Suppose you have:
> 	protocol Randomizer {
>> 		mutating func choice<T: FloatingPoint>(from choices: ClosedRange<T>) -> T
>> 	}
> Which lets you say:
> 	rng.choice(from: 0...1)
> It would then be nice to say something like:
> 	rng.choice(from: Double.positives)
> 	rng.choice(from: Float.finites)
> 	// etc.
> Of course, the set of ranges useful for that purpose is probably different from these more technical things, so that might not be the best design anyway.
>>>> public protocol FloatingPoint: SignedArithmetic, Comparable {
>>>> func isLess(than other: Self) -> Bool
>>>> func totalOrder(with other: Self) -> Bool
>>> Swift 2's Comparable demands a strict total order. However, the
>>> documentation here seems to imply that totalOrder is *not* what you
>>> get from the < operator. Is something getting reshuffled here?
>> The Swift 2 Comparable documentation is probably overly specific.
>> The requirement should really be something like a strict total order
>> on non-exceptional values.
> Okay.
> (While I was waiting for your reply, I was thinking about a rework of
> `Comparable` which had a sort-oriented `totalOrder(_:)` as a
> requirement and `isLess(_:)`, `isGreater(_:)`, et. al. which defaulted
> to using `totalOrder(_:)`, but could be overridden for types like
> FloatingPoint with exceptional values. I should just wait for answers
> sometimes.)

We're still interested in doing something like that.

>>> Also, since `init(_:)` is lossy and `init(exactly:)` is not,
>>> shouldn't their names technically be switched? Or perhaps
>>> `init(_:)` should be exact and trapping, `init(exactly:)` should be
>>> failable, and `init(closest:)` should always return something or
>>> other?
>> That would be a large change in the existing behavior, since we only
>> have the (potentially lossy) init(_:) today.  It would also be
>> something of a surprise to users of C family languages.  That’s not
>> to say that it’s necessarily wrong, but it would break a lot of
>> people’s code an expectations, and I was trying to make this
>> proposal fairly benign, with a focus on adding missing features.
> Sure, but it's also a surprise when integers don't roundtrip through
> floats. (See: every JSON-based API which had to add an "id_str" field
> when their ID numbers got too big.) I'm not entirely certain it's the
> right move—you're right that people with a C background wouldn't
> expect it—but silently doing the wrong thing is a bit suspicious in
> Swift.


More information about the swift-evolution mailing list