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

Stephen Canon scanon at apple.com
Fri Apr 15 00:07:45 CDT 2016


Hi Brent, thanks for the feedback.

> On Apr 14, 2016, at 8:34 PM, Brent Royal-Gordon <brent at architechies.com> wrote:
> 
> First of all: I do *not* do crazy things with floating-point numbers, so there's a good chance I'm missing the point with some of this. Consider anything having to do with NaNs, subnormals, or other such strange denizens of the FPU to be prefixed with "I may be totally missing the point, but…”

Noted.

>> public protocol Arithmetic
> 
> Is there a rationale for the name "Arithmetic"? There's probably nothing wrong with it, but I would have guessed you'd use the name "Number”.

Dave A. came up with the name, though I think it’s a good one.  Number isn’t bad either.

>> : Equatable, IntegerLiteralConvertible
> 
> 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.

>> func adding(rhs: Self) -> Self
>> mutating func add(rhs: Self)
> 
> Is there a reason we're introducing methods and calling them from the operators, rather than listing the operators themselves as requirements?
> 
>>  func negate() -> Self
> 
> Should this be `negated()`? Should there be a mutating `negate()` variant, even if we won't have an operator for it?
> 
> (If a mutating `negate` would be an attractive nuisance, we can use `negative()`/`formNegative()` instead.)

Chris noted this too, and I think you’re both right.

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

>> static var greatestFiniteMagnitude: Self { get }
>> static var leastNormalMagnitude: Self { get }
>> static var leastMagnitude: Self { get }
> 
> 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.

> I wonder if instead, we can use ClosedIntervals/ClosedRanges to group together related values:
> 
> 	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
> 
> 	// Alternatively, you could have `positives`, running from 0.nextUp to infinity
> 
> Technically, you could probably implement calls like e.g. isNormal in terms of the positiveNormals property, but I'm sure separate calls are much, much faster.
> 
> (It might also be helpful if you could negate signed ClosedIntervals, which would negate and swap the bounds.)

This seems wildly over-engineered to me personally.  Every language I surveyed provides (a subset of) these quantities as simple scalar values.

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

>>  init<Source: Integer>(_ value: Source)
>>  init?<Source: Integer>(exactly value: Source)
> 
>>  init<Source: BinaryFloatingPoint>(_ value: Source)
>>  init?<Source: BinaryFloatingPoint>(exactly value: Source)
> 
> It's great to have both of these, but I wonder how they're going to be implemented—if Integer can be either signed or unsigned, I'm not sure how you get the top bit of an unsigned integer out.

These are the bits that are dependent on the new Integer proposal, which should be forthcoming soonish.  As you note, they aren’t really implementable without it (at least, not easily).  However, I thought that it made the most sense to include them with this proposal.

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

– Steve


More information about the swift-evolution mailing list