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

Brent Royal-Gordon brent at architechies.com
Fri Apr 15 02:02:04 CDT 2016

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

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


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

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

Brent Royal-Gordon

More information about the swift-evolution mailing list