[Review] SE-0067: Enhanced Floating Point Protocols


(Chris Lattner) #1

Hello Swift community,

The review of "SE-0067: Enhanced Floating Point Protocols" begins now and runs through April 25. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md

Reviews are an important part of the Swift evolution process. All reviews should be sent to the swift-evolution mailing list at

  https://lists.swift.org/mailman/listinfo/swift-evolution

or, if you would like to keep your feedback private, directly to the review manager.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  * What is your evaluation of the proposal?
  * Is the problem being addressed significant enough to warrant a change to Swift?
  * Does this proposal fit well with the feel and direction of Swift?
  * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

  https://github.com/apple/swift-evolution/blob/master/process.md

Thank you,

-Chris Lattner
Review Manager


(Xiaodi Wu) #2

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than"

Since `adding(_:)` is instead now an operator in the current version
of the proposal, could comparison functions also be operators only?

Incorrect nomenclature in an attempt to correct previously misleading
nomenclature:
* `leastMagnitude` should be `leastPositive` or `minPositive`, because
magnitudes can be zero: it's bonkers that
`Double.minimumMagnitude(0.0, Double.leastMagnitude) <
Double.leastMagnitude`!
(Likewise, `leastNormalMagnitude` should be `leastNormalPositive` or
`minPositive`, and `greatestFiniteMagnitude` should be
`greatestFinite` or `maxFinite`)

Inconsistencies with Integer protocols (at least, as they are currently):
* properties named "least..." or "greatest..." are inconsistent with
conceptually similar properties such as `Int.min` and `Int.max`

Use of one term-of-art (`ulp`), as Swift naming guidelines allow and
even encourage, but failure to use more widely understood terms of
art:
* `squareRoot()` should be `sqrt()`
* something really ought to be done about
`truncatingRemainder(dividingBy:)`--the fact that the comments tell
the reader that `truncatingRemainder(dividingBy:)` is equivalent to C
`fmod` suggests to me that `fmod` may be a widely understood
term-of-art

I argue strongly that Swift's naming guidelines about terms-of-art
should be understood to encourage terms used widely in other languages
for basic mathematical functions instead of written-out English
equivalents--e.g. `asinh` instead of`inverseHyperbolicSine`. Looking
to other C-style languages, all seem to accept these shorter terms
as-is without writing them out.

* Is the problem being addressed significant enough to warrant a
change to Swift?

Yes. Several pain points exist with current FloatingPoint protocols
that are addressed in this proposal.

* Does this proposal fit well with the feel and direction of Swift?

Partially. In addressing pain points, yes. Some of the proposed method
names could stand improvement for accuracy and for consistency with
the rest of the language, as outlined above.

* If you have you used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?

I believe this proposal brings the FloatingPoint protocol up to a
similar level of comprehensiveness as the approximate equivalents in
C-family languages such as C# and Java; however, I think some further
clean-up of the proposed names should be considered.

* How much effort did you put into your review? A glance, a quick
reading, or an in-depth study?

Followed the original discussion of draft proposal carefully; have
used floating point protocols extensively in (current) Swift and in
other languages.


(Daniel Vollmer) #3

Hello Swift community,

The review of "SE-0067: Enhanced Floating Point Protocols" begins now and runs through April 25. The proposal is available here:

  https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md

This looks pretty nice, but I have to admit I’m unaware of pretty much anything about the current floating-point protocols.

A question, if I may:

/// A signalling NaN (not-a-number).
@warn_unused_result
static fun signalingNaN: Self { get }

Why is this a static func compared to a static var for nan? Representation only known at run-time? Why is the second N in NaN capitalised?

  Daniel.

···

On 19 Apr 2016, at 23:16, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:


(Howard Lovatt) #4

The review of "SE-0067: Enhanced Floating Point Protocols" begins now and
runs through April 25. The proposal is available here:

https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md

        * What is your evaluation of the proposal?

+1, a valuable addition, with a few details I think could be improved:

    1. Consistent naming:
        1.a. Use the xxxed form of naming rather than mixing xxxed and
xxxing, e.g. in Arithmetic use added instead of adding to be consistent
with divided etc. Both name forms are allowed in the guidelines but it is
odd to mix them in the same API. Also see point 2 below.
        1.b. Use isXxx or hasXxx if that reads better for methods that
return Bool
    2. For the Arithmetic protocol use symbols instead of names, e.g.
`infix +` instead of `added`.

        * Is the problem being addressed significant enough to warrant a
change to Swift?

Yes, I have needed this a number of times

        * Does this proposal fit well with the feel and direction of Swift?

Yes

        * If you have you used other languages or libraries with a similar
feature, how do you feel that this proposal compares to those?

Yes, e.g. Java, very handy

        * How much effort did you put into your review? A glance, a quick
reading, or an in-depth study?

Read the proposal and corresponded on Swift-Evolution and previously on the
old Apple-dev forums about this issue.


(Jordan Rose) #5

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md]

This is super impressive. I do have several bits I’m uncomfortable with, however. I’ll try to separate that into “semantic” and “naming” sections.

Semantic

static var radix: Int { get }

Does it ever make sense to have a model type that allows different instances to have different radices?

Is there an algorithm that makes use of a model’s radix, or is this just in here for “completeness”?

  /// A signaling NaN (not-a-number).
  @warn_unused_result
  static func signalingNaN: Self { get }

I’m not sure it really makes sense for a Bignum / APFloat type to support such a value. But really I think this is just underspecified. What does it mean, in terms of this protocol and its uses, for a NaN to be signaling? Is it just a specific “color" of NaN, with no semantic requirements other than being distinguishable?

(Also, is ‘signalingNan.isNan’ true? I assume so but since ’nan’ is implied to be a non-signaling NaN I’m not sure anymore.)

  var signBit: Bool { get }

Unlike Chris, I’m strongly against this property as it stands. You should not be able to write “if someValue.signBit”; a bit is not a boolean value. (Citation: "Uses of Boolean methods and properties should read as assertions about the receiver <https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage>.”)

I’d be okay with Greg’s idea of changing the type to an enum. I’d also be okay with renaming this to a predicate, whatever the name ends up being. (“isSignBitSet”, “isSignNegative”, etc.)

  var exponent: Int { get }

Nitpick: it’s probably worth noting in the doc comment that this is the unbiased exponent value.

Also, does it matter that this is insufficient for bignums, which may have an exponent of greater than `sizeof(Int.self)` bits? (This is also a concern for a number of members of BinaryFloatingPoint, like ‘significantBitCount'.)

Naming

On “NaN” vs. “Nan”: I’m not convinced that ignoring the case is the right way to go here. IMHO the clearest lowercase form is “nan” and the clearest capitalized form is “NaN”.

The current draft API guidelines <https://swift.org/documentation/api-design-guidelines/#general-conventions> don’t cover this case, but if I were to add something for this, I’d say “when a word is normally written with mixed case, the lowercase form should be fully-lowercased if the first letter is naturally uppercase, and the capitalized form should have the first letter uppercased only.” That rule produces “iPhone/IPhone”, “next/NeXT”, and “nan/NaN”. (The “if the first letter is naturally uppercase” could be thrown out as well.)

On 'isLessThanOrEqual(to:)’: I agree with Xiaodi that the argument label is problematic here. I think the problem is that we have two prepositions that apply to the argument, and “pick the second one” leaves the base name feeling unbalanced. (Remember that we allow referring to a method by its basename alone when using it as a function value.)

On 'isTotallyOrdered(with:)’: I lost track of who said it, but I agree that this sounds like it’s “!isUnordered(with: other)”. The only name that’s coming to mind is ‘isTotallyOrderedBefore(_:)’, which isn’t great.

On ‘binade’: At first I thought this was a confusing term and there had to be a better one, but now I think it’s an “if you don’t know what this is, you don’t need to use it” case. :slight_smile:

Jordan


(Brent Royal-Gordon) #6

A few things…

First, something absent: I'm a little bit concerned by the act that there's no means to convert between different concrete FloatingPoint types. Something like the IntMax mechanism in Swift 2's IntegerType might be useful, though there may be good reasons not to do that.

public protocol FloatingPoint: Comparable, IntegerLiteralConvertible {
public protocol BinaryFloatingPoint: FloatingPoint, FloatLiteralConvertible {

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

  prefix func +(x: Self) -> Self

Does this actually do anything, or is it a no-op? If the latter, should it just be a free function rather than part of the protocol? Or maybe even part of the literal syntax that can't be applied to variables at all?

  static func minimum(x: Self, _ y: Self) -> Self
  static func maximum(x: Self, _ y: Self) -> Self
  static func minimumMagnitude(x: Self, _ y: Self) -> Self
  static func maximumMagnitude(x: Self, _ y: Self) -> Self

These all take two operands, but I can't think of a reason for that. Might they be better off as variadic? (Or is the "implementation hook" comment meant to indicate that the variadic form of the `max` free function will use these calls?)

  func isEqual(to other: Self) -> Bool
  func isLess(than other: Self) -> Bool
  func isLessThanOrEqual(to other: Self) -> Bool

I'm still not sure why these are methods instead of operators.

(I also share others' concern about the argument label on `isLessThanOrEqual`.)

  func isUnordered(with other: Self) -> Bool
  func isTotallyOrdered(with other: Self) -> Bool

I think these features should be folded into Comparable, but I'll start a different thread about that. For the moment, these seem fine.

  func isEqual<Other: BinaryFloatingPoint>(to other: Other) -> Bool
  func isLess<Other: BinaryFloatingPoint>(than other: Other) -> Bool
  func isLessThanOrEqual<Other: BinaryFloatingPoint>(to other: Other) -> Bool
  func isUnordered<Other: BinaryFloatingPoint>(with other: Other) -> Bool
  func isTotallyOrdered<Other: BinaryFloatingPoint>(with other: Other) -> Bool

Can you provide some color on these? Loosening the typing in this way is quite unusual in Swift; you're not doing it for arithmetic operators, for instance.

  init(nan payload: Self.RawSignificand, signaling: Bool)

The discussion doesn't mention why this isn't part of the protocol. Any commentary? (I'm guessing it's "you need to know the type anyway to do anything useful with the payload", but I'm not sure, and it's useful to document the reasons for these sorts of decisions)

  * What is your evaluation of the proposal?

I think it's a huge improvement.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. The current floating-point protocols are inadequate.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I don't have much experience with floating-point abstractions of this sort, but as far as the end-user APIs are concerned, I really like these APIs.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Participated in the previous round of discussion as well as this one.

···

--
Brent Royal-Gordon
Architechies


(Stephen Canon) #7

This is a typo in the changes that were made to the draft. It should be:

  static var signalingNan: Self { get }

– Steve

···

On Apr 19, 2016, at 6:11 PM, Daniel Vollmer via swift-evolution <swift-evolution@swift.org> wrote:

Why is this a static func compared to a static var for nan? Representation only known at run-time? Why is the second N in NaN capitalised?


(Stephen Canon) #8

Hi Xiaodi —

Thanks for the detailed comments. Some thoughts inline.

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

This is a typo. Should be signalingNan.

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than”

Since `adding(_:)` is instead now an operator in the current version
of the proposal, could comparison functions also be operators only?

They could, but you still need isUnordered(with: ) and isTotallyOrdered(with: ), as they don’t have operator equivalents.

Incorrect nomenclature in an attempt to correct previously misleading
nomenclature:
* `leastMagnitude` should be `leastPositive` or `minPositive`, because
magnitudes can be zero: it's bonkers that
`Double.minimumMagnitude(0.0, Double.leastMagnitude) <
Double.leastMagnitude`!
(Likewise, `leastNormalMagnitude` should be `leastNormalPositive` or
`minPositive`, and `greatestFiniteMagnitude` should be
`greatestFinite` or `maxFinite`)

Inconsistencies with Integer protocols (at least, as they are currently):
* properties named "least..." or "greatest..." are inconsistent with
conceptually similar properties such as `Int.min` and `Int.max`

`min` and `max` were deliberately avoided based on a discussion with the Apple standard library team; these properties don’t really behave like the integer bounds properties, so naming them similarly may be confusing.

Your point about magnitudes being non-zero is reasonable, but I think you’ve taken it a step to far; it could be corrected by simply changing `leastMagnitude` to either `leastPositiveMagnitude` or `leastNonzeroMagnitude`.

`leastNormalMagnitude` and `greatestFiniteMagnitude` are accurate as is. (`minPositive`, on the other hand, would be exceedingly misleading). Can you expand on why you want to change them? It seems like you simply prefer “positive” to “magnitude”?

Use of one term-of-art (`ulp`), as Swift naming guidelines allow and
even encourage, but failure to use more widely understood terms of
art:
* `squareRoot()` should be `sqrt()`
* something really ought to be done about
`truncatingRemainder(dividingBy:)`--the fact that the comments tell
the reader that `truncatingRemainder(dividingBy:)` is equivalent to C
`fmod` suggests to me that `fmod` may be a widely understood
term-of-art
I argue strongly that Swift's naming guidelines about terms-of-art
should be understood to encourage terms used widely in other languages
for basic mathematical functions instead of written-out English
equivalents--e.g. `asinh` instead of`inverseHyperbolicSine`. Looking
to other C-style languages, all seem to accept these shorter terms
as-is without writing them out.

sqrt( ) I could support, but fmod( ) is an absolutely terrible name for a relatively rarely-used function. If there were a good term-of-art, I would want to use it, but AFAIK there isn’t.

I should note that the free functions sqrt( ) and fmod( ) won’t go away with this proposal. They will continue to be supplied by the math overlay for Float, Double, CGFloat, just not for the FloatingPoint protocol. So why do we need them in the FloatingPoint protocol at all?

The squareRoot( ) and remainder( ) methods are distinct from most of the other <math.h> functions in that IEEE 754 considers them to be "basic operations” as defined by clause 5 of the standard (IEEE 754 spells out the name “squareRoot” FWIW, though there’s no requirement that we follow that). Because of this it makes sense to require them as methods in the FloatingPoint protocol, rather than only as free functions. [truncatingRemainder( ) is not required by IEEE 754, but it doesn’t impose a significant implementation burden and eases the transition for folks currently using operator %.]

– Steve

···

On Apr 19, 2016, at 6:34 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:


(Tim Hawkins) #9

"NaN" with the shown capitalisation is the standard way of showing this
constant in every other language that I know of, changing it to "Nan" would
cause confusion and risks people not recognising it for what it is. The
form is reasonable given that it is the acronym for "Not a Number".

···

On Apr 20, 2016 6:12 AM, "Daniel Vollmer via swift-evolution" < swift-evolution@swift.org> wrote:

> On 19 Apr 2016, at 23:16, Chris Lattner via swift-evolution < > swift-evolution@swift.org> wrote:
>
> Hello Swift community,
>
> The review of "SE-0067: Enhanced Floating Point Protocols" begins now
and runs through April 25. The proposal is available here:
>
>
https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md

This looks pretty nice, but I have to admit I’m unaware of pretty much
anything about the current floating-point protocols.

A question, if I may:
> /// A signalling NaN (not-a-number).
> @warn_unused_result
> static fun signalingNaN: Self { get }

Why is this a static func compared to a static var for nan? Representation
only known at run-time? Why is the second N in NaN capitalised?

        Daniel.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Dave Abrahams) #10

That seems like a huge stretch to me. Are you claiming it's wrong to
say “x is less than or equal to y,” or are you saying something else?

···

on Tue Apr 19 2016, Xiaodi Wu <swift-evolution@swift.org> wrote:

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than"

--
Dave


(James Berry) #11

Maybe: isLessThanOrEqualTo(value:) ??

···

On Apr 21, 2016, at 6:13 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org> wrote:

On 'isLessThanOrEqual(to:)’: I agree with Xiaodi that the argument label is problematic here. I think the problem is that we have two prepositions that apply to the argument, and “pick the second one” leaves the base name feeling unbalanced. (Remember that we allow referring to a method by its basename alone when using it as a function value.)


(Stephen Canon) #12

[Proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md]

This is super impressive. I do have several bits I’m uncomfortable with, however. I’ll try to separate that into “semantic” and “naming” sections.

Semantic

static var radix: Int { get }

Does it ever make sense to have a model type that allows different instances to have different radices?

No.

Is there an algorithm that makes use of a model’s radix, or is this just in here for “completeness”?

If you know ulp, radix, and exponent range, you can infer basically all the other numerical details of the type. One good example would be the constant that Dave mentioned, “maxResultOfAdding1”. You can compute this if you know radix and ulp. The radix is also the bound on how large the relative spacing between consecutive numbers can get, which is sometimes important for computing accurate bounds. These are all somewhat niche, but the problem is that there’s no good way to get this value if you don’t have it, and it imposes “zero” implementation burden.

  /// A signaling NaN (not-a-number).
  @warn_unused_result
  static func signalingNaN: Self { get }

I’m not sure it really makes sense for a Bignum / APFloat type to support such a value. But really I think this is just underspecified. What does it mean, in terms of this protocol and its uses, for a NaN to be signaling? Is it just a specific “color" of NaN, with no semantic requirements other than being distinguishable?

There are a variety of means that a softfloat type could use to implement signaling NaNs. Here are two of the simpler ones:

(a) if running on HW with hard-float support, use native-precision hard-float instructions to set flags as needed.
(b) provide operation variants that take an inout flags / parameter:

  mutating func add(rhs: Self, inout flags: Flags)

(Also, is ‘signalingNan.isNan’ true? I assume so but since ’nan’ is implied to be a non-signaling NaN I’m not sure anymore.)

Yup, that should be clarified.

  var signBit: Bool { get }

Unlike Chris, I’m strongly against this property as it stands. You should not be able to write “if someValue.signBit”; a bit is not a boolean value. (Citation: "Uses of Boolean methods and properties should read as assertions about the receiver <https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage>.”)

I’d be okay with Greg’s idea of changing the type to an enum. I’d also be okay with renaming this to a predicate, whatever the name ends up being. (“isSignBitSet”, “isSignNegative”, etc.)

Making it a predicate is weird, because then the three properties making up the number become `isSignBitSet`, `exponent`, and `significand`; one of these things is not like the other ones. If `signBit: Bool` were ruled out, I would rather go with Greg’s enum proposal.

  var exponent: Int { get }

Nitpick: it’s probably worth noting in the doc comment that this is the unbiased exponent value.

Also, does it matter that this is insufficient for bignums, which may have an exponent of greater than `sizeof(Int.self)` bits? (This is also a concern for a number of members of BinaryFloatingPoint, like ‘significantBitCount’.)

An exponent of Int.max encodes a number >= 2**Int.max. This is a staggeringly huge quantity, even when Int is 32 bits (it’s approximately 1e646456992). There are a few extremely niche applications that require numbers with greater magnitude, but they are *extremely* rare. To a good approximation, `Int` is more than enough bits, and a reasonable tradeoff.

Ditto `significandBitCount`. I haven’t seen usage of floating-point types with more than a few thousand significand bits; billions of bits is enough. It is plausible that one could build a type that runs into this limit on a 32-bit system, but it wouldn’t be very useful; on a 64-bit system, you can’t allocate the storage for even one such value.

Naming

On “NaN” vs. “Nan”: I’m not convinced that ignoring the case is the right way to go here. IMHO the clearest lowercase form is “nan” and the clearest capitalized form is “NaN”.

The current draft API guidelines <https://swift.org/documentation/api-design-guidelines/#general-conventions> don’t cover this case, but if I were to add something for this, I’d say “when a word is normally written with mixed case, the lowercase form should be fully-lowercased if the first letter is naturally uppercase, and the capitalized form should have the first letter uppercased only.” That rule produces “iPhone/IPhone”, “next/NeXT”, and “nan/NaN”. (The “if the first letter is naturally uppercase” could be thrown out as well.)

Yup, this seems like a sensible rule to me.

On 'isLessThanOrEqual(to:)’: I agree with Xiaodi that the argument label is problematic here. I think the problem is that we have two prepositions that apply to the argument, and “pick the second one” leaves the base name feeling unbalanced. (Remember that we allow referring to a method by its basename alone when using it as a function value.)

On 'isTotallyOrdered(with:)’: I lost track of who said it, but I agree that this sounds like it’s “!isUnordered(with: other)”. The only name that’s coming to mind is ‘isTotallyOrderedBefore(_:)’, which isn’t great.

On ‘binade’: At first I thought this was a confusing term and there had to be a better one, but now I think it’s an “if you don’t know what this is, you don’t need to use it” case. :slight_smile:

Yup.

– Steve

···

On Apr 21, 2016, at 9:13 PM, Jordan Rose <jordan_rose@apple.com> wrote:


(Rainer Brockerhoff) #13

While I'm not competent to comment on the other points, I would venture
to suggest that, in order to accomodate a future Decimal type (and
DecimalFloatingPoint protocol), a BinaryFloatLiteralConvertible may be a
necessary subset of FloatLiteralConvertible - as some literals don't
have a lossless binary representation, etc.

···

On 4/23/16 21:53, Brent Royal-Gordon via swift-evolution wrote:

public protocol FloatingPoint: Comparable, IntegerLiteralConvertible {
public protocol BinaryFloatingPoint: FloatingPoint, FloatLiteralConvertible {

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

--
Rainer Brockerhoff <rainer@brockerhoff.net>
Belo Horizonte, Brazil
"In the affairs of others even fools are wise
In their own business even sages err."
http://brockerhoff.net/blog/


(Nicola Salmoria) #14

> func isEqual(to other: Self) ->Bool
> func isLess(than other: Self) ->Bool
> func isLessThanOrEqual(to other: Self) ->Bool

I'm still not sure why these are methods instead of operators.

I think this is an *excellent* choice, and I hope it is the first step to completely removing operators from protocols.

IMHO throwing operators into protocols is inconsistent and confusing. Having regular methods and a single generic version of the operator that calls down on the type’s methods is clearer and guarantees that generic code can avoid ambiguities by calling the methods directly, instead of having to rely only on heavily overloaded global operators.

···


Nicola


(Stephen Canon) #15

A few things…

First, something absent: I'm a little bit concerned by the act that there's no means to convert between different concrete FloatingPoint types. Something like the IntMax mechanism in Swift 2's IntegerType might be useful, though there may be good reasons not to do that.

There are concrete conversions between all built in float types. Those aren’t going away. Are you saying that you want init(_ value: Float) etc to be protocol requirements?

public protocol FloatingPoint: Comparable, IntegerLiteralConvertible {
public protocol BinaryFloatingPoint: FloatingPoint, FloatLiteralConvertible {

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

It doesn’t make sense for non-radix-2 types; you would change bases multiple times.

prefix func +(x: Self) -> Self

Does this actually do anything, or is it a no-op? If the latter, should it just be a free function rather than part of the protocol? Or maybe even part of the literal syntax that can't be applied to variables at all?

static func minimum(x: Self, _ y: Self) -> Self
static func maximum(x: Self, _ y: Self) -> Self
static func minimumMagnitude(x: Self, _ y: Self) -> Self
static func maximumMagnitude(x: Self, _ y: Self) -> Self

These all take two operands, but I can't think of a reason for that. Might they be better off as variadic? (Or is the "implementation hook" comment meant to indicate that the variadic form of the `max` free function will use these calls?)

Correct, the free functions will use these.

func isEqual(to other: Self) -> Bool
func isLess(than other: Self) -> Bool
func isLessThanOrEqual(to other: Self) -> Bool

I'm still not sure why these are methods instead of operators.

(I also share others' concern about the argument label on `isLessThanOrEqual`.)

There’s lots of concern, and very few suggestions of anything better. The only thing that seems workable is maybe `isLessThanOrEqualTo(_:)`, which is inconsistent with the other comparisons, but maybe that’s the right thing to do anyway.

func isUnordered(with other: Self) -> Bool
func isTotallyOrdered(with other: Self) -> Bool

I think these features should be folded into Comparable, but I'll start a different thread about that. For the moment, these seem fine.

func isEqual<Other: BinaryFloatingPoint>(to other: Other) -> Bool
func isLess<Other: BinaryFloatingPoint>(than other: Other) -> Bool
func isLessThanOrEqual<Other: BinaryFloatingPoint>(to other: Other) -> Bool
func isUnordered<Other: BinaryFloatingPoint>(with other: Other) -> Bool
func isTotallyOrdered<Other: BinaryFloatingPoint>(with other: Other) -> Bool

Can you provide some color on these? Loosening the typing in this way is quite unusual in Swift; you're not doing it for arithmetic operators, for instance.

Heterogeneous comparisons have an unambiguous result type and correct result. Heterogeneous arithmetic does not (at least, not in all cases), so it’s a more difficult language design problem.

init(nan payload: Self.RawSignificand, signaling: Bool)

The discussion doesn't mention why this isn't part of the protocol. Any commentary? (I'm guessing it's "you need to know the type anyway to do anything useful with the payload", but I'm not sure, and it's useful to document the reasons for these sorts of decisions)

Yup, that’s exactly right. I’ll add a note.

  * What is your evaluation of the proposal?

I think it's a huge improvement.

  * Is the problem being addressed significant enough to warrant a change to Swift?

Yes. The current floating-point protocols are inadequate.

  * Does this proposal fit well with the feel and direction of Swift?

Yes.

  * If you have you used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?

I don't have much experience with floating-point abstractions of this sort, but as far as the end-user APIs are concerned, I really like these APIs.

  * How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

Participated in the previous round of discussion as well as this one.

Thanks for the feedback!
– Steve

···

On Apr 23, 2016, at 8:53 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:


(David Sweeris) #16

I'm inclined to agree.

- Dave Sweeris

···

On Apr 19, 2016, at 7:50 PM, Tim Hawkins via swift-evolution <swift-evolution@swift.org> wrote:

"NaN" with the shown capitalisation is the standard way of showing this constant in every other language that I know of, changing it to "Nan" would cause confusion and risks people not recognising it for what it is. The form is reasonable given that it is the acronym for "Not a Number".


(Xiaodi Wu) #17

Hi Xiaodi —

Thanks for the detailed comments. Some thoughts inline.

* What is your evaluation of the proposal?

+1 in intent. Specifics require further refinement. For example:

Internal inconsistencies in capitalization:
* `signalingNaN` but `isSignalingNan` and `isNan`

This is a typo. Should be signalingNan.

Parameter labels, or whatever they're called now, do not reflect newly
adopted Swift syntax in SE-0046:
* `static func maximum(x: Self, _ y: Self) -> Self` should be `static
func maximum(_ x: Self, _ y: Self) -> Self`, etc.

Infelicitous use of prepositions to conform superficially to new
naming guidelines:
* `isEqual(to:)` is fine, but for consistency there's
`isLessThanOrEqual(to:)`, which is not fine, because the preposition
"to" applies only to "equal" and not to "less than”

Since `adding(_:)` is instead now an operator in the current version
of the proposal, could comparison functions also be operators only?

They could, but you still need isUnordered(with: ) and isTotallyOrdered(with: ), as they don’t have operator equivalents.

My two cents are that the comparison functions that have operators
should stick with those operators only instead of the names.
Stylistically, I think `isLessThanOrEqual(to:)` is less than
cromulent.

Some thoughts on `isUnordered(with:)` and `isTotallyOrdered(with:)`--

1. As you mention below, implementations are free to expose
IEEE754-mandated operations with names that differ from the
specification. Looking to Java and C# as examples of how other C-style
languages implement floating point types, C-style languages have taken
a free hand in how they deal with comparison with NaN.

So long as we're not going down the road of having methods that expose
distinct unordered-quiet and unordered-signaling comparison predicates
as defined in IEEE754 (do any C-style languages do that?), there's
probably some freedom to determine whether `isUnordered(with:)` is
necessary as a standalone method or whether it's already sufficiently
provided for by other methods. In the end, `x.isUnordered(with: y)` is
really equivalent to `x.isNan || y.isNan`, which can be immediately
grokked by any reader of code.

2. `isTotallyOrdered(with:)` isn't quite apt as a name. As I
understand it, totalOrder(x, y) "reflects" a total ordering such that,
when x is ordered "below" y, the return value is true. (Both quoted
words reflect the terminology used in the specification.) So perhaps
`isTotallyOrdered(below:)` or even just `isOrdered(below:)` would be
more apt. Certainly `with` is not the correct label for the parameter,
as any two values that don't compare unordered should be "totally
ordered with" each other. This is especially problematic if we're
going to keep `isUnordered(with:)`, since those two methods would
suggest a complementary logical relationship that just isn't there.

Incorrect nomenclature in an attempt to correct previously misleading
nomenclature:
* `leastMagnitude` should be `leastPositive` or `minPositive`, because
magnitudes can be zero: it's bonkers that
`Double.minimumMagnitude(0.0, Double.leastMagnitude) <
Double.leastMagnitude`!
(Likewise, `leastNormalMagnitude` should be `leastNormalPositive` or
`minPositive`, and `greatestFiniteMagnitude` should be
`greatestFinite` or `maxFinite`)

Inconsistencies with Integer protocols (at least, as they are currently):
* properties named "least..." or "greatest..." are inconsistent with
conceptually similar properties such as `Int.min` and `Int.max`

`min` and `max` were deliberately avoided based on a discussion with the Apple standard library team; these properties don’t really behave like the integer bounds properties, so naming them similarly may be confusing.

I agree absolutely that `min` and `max`, standalone, are misleading
and shouldn't be used. That said, I would argue that `maxFinite` is
demonstrably the correct name because it's the most succinct one
that's both accurate and consistent with other usage. Adding
"magnitude" clarifies nothing here and in fact made me do a
double-take on first reading: I had to think for a split second
whether there exists a greater floating point value that's a finite
non-magnitude: it's of course absurd, but you have to think about it
the first time. Meanwhile, `greatestFinite` means exactly the same
thing as `maxFinite`, but now you're introducing a different word
where it's not necessary to clarify the meaning or differentiate from
standalone `max`.

Your point about magnitudes being non-zero is reasonable, but I think you’ve taken it a step to far; it could be corrected by simply changing `leastMagnitude` to either `leastPositiveMagnitude` or `leastNonzeroMagnitude`.

By the same reasoning here, `minPositive` (instead of
`leastMagnitude`) is equally accurate, more consistent with usage
elsewhere, and probably more accessible to non-native English
speakers. Throughout the IEEE specification, "magnitude" is used in
relation to absolute values, which is not really in play here.

In any case, we agree that `leastMagnitude` must at minimum be renamed
to exclude zero.

`leastNormalMagnitude` and `greatestFiniteMagnitude` are accurate as is. (`minPositive`, on the other hand, would be exceedingly misleading). Can you expand on why you want to change them? It seems like you simply prefer “positive” to “magnitude”?

I made a typo here (I blame jetlag): `minPositiveNormal` was what I
meant to type. Again, my rationale is that it is the least deviation
from `min` that accurately describes what's going on using the
simplest words. Here, the distinction between normal and subnormal is
unavoidable, but we don't need to talk about magnitudes, and there
really is no difference between "min" and "least".

Also potentially useful (actually, definitely useful in implementing
floating point strides) would be the properties `maxExactInteger`
(and, I suppose, a corresponding `minExactInteger`).

Use of one term-of-art (`ulp`), as Swift naming guidelines allow and
even encourage, but failure to use more widely understood terms of
art:
* `squareRoot()` should be `sqrt()`
* something really ought to be done about
`truncatingRemainder(dividingBy:)`--the fact that the comments tell
the reader that `truncatingRemainder(dividingBy:)` is equivalent to C
`fmod` suggests to me that `fmod` may be a widely understood
term-of-art
I argue strongly that Swift's naming guidelines about terms-of-art
should be understood to encourage terms used widely in other languages
for basic mathematical functions instead of written-out English
equivalents--e.g. `asinh` instead of`inverseHyperbolicSine`. Looking
to other C-style languages, all seem to accept these shorter terms
as-is without writing them out.

sqrt( ) I could support, but fmod( ) is an absolutely terrible name for a relatively rarely-used function. If there were a good term-of-art, I would want to use it, but AFAIK there isn’t.

I should note that the free functions sqrt( ) and fmod( ) won’t go away with this proposal. They will continue to be supplied by the math overlay for Float, Double, CGFloat, just not for the FloatingPoint protocol. So why do we need them in the FloatingPoint protocol at all?

The squareRoot( ) and remainder( ) methods are distinct from most of the other <math.h> functions in that IEEE 754 considers them to be "basic operations” as defined by clause 5 of the standard (IEEE 754 spells out the name “squareRoot” FWIW, though there’s no requirement that we follow that).

Even IEEE754 is interestingly inconsistent here. Throughout, it's
written as "squareRoot", but when it comes to "recommended" functions,
the reciprocal of the square root is written "rSqrt" (see table 9.1).
I'd highly recommend setting a good example of when things are
terms-of-art by going with "sqrt".

While we're on the topic of IEEE754 section 5 functions, we're missing
abs(x) in the protocol.

And speaking of absolute value functions, IEEE754 calls it "minNumMag"
and "maxNumMag" when comparing two values but, when it comes to
summing, recommends a function called "sumAbs". A missed opportunity
for consistency, IMO. When implementing in Swift, then, was there a
rationale for having `minimumMagnitude` instead of the much shorter
but equally grokkable `minAbs` (abs being, IMO, a term-of-art like
sqrt)?

And then speaking of implementations of IEEE minNum and maxNum--are
those static methods necessary in the protocol? What is gained over
having just the comparison operators?

Because of this it makes sense to require them as methods in the FloatingPoint protocol, rather than only as free functions. [truncatingRemainder( ) is not required by IEEE 754, but it doesn’t impose a significant implementation burden and eases the transition for folks currently using operator %.]

I'm not sure I buy the "ease" argument. Surely, if folks are using
operator %, it's equivalently difficult to transition either to the
free function or to `truncatingRemainder()`? Suppose you deprecate %
and recommend fmod(), then just leave `truncatingRemainder()` out of
the protocol. After all, the rationale for deprecation is that people
aren't using it correctly...

···

On Wed, Apr 20, 2016 at 5:38 AM, Stephen Canon <scanon@apple.com> wrote:

On Apr 19, 2016, at 6:34 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

– Steve


(Xiaodi Wu) #18

I'm saying something else. The preposition that goes with "less" is "than,"
while "to" goes only with "equal." By making "to" a parameter label you've
got {less than or equal} to, which is inelegant because the label cannot be
distributed to both parts--i.e. one cannot say "less than to or equal to."

Put another way, I could just as well rewrite the method as
`equalToOrLess(than:)`. Now, the parameter is labeled "than" instead of
"to," yet the parameter serves the same purpose. Thus, I argue that the
proposed method name may meet the letter of the Swift guidelines but is
awkward.

···

On Wed, Apr 20, 2016 at 14:44 Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

on Tue Apr 19 2016, Xiaodi Wu <swift-evolution@swift.org> wrote:

> * What is your evaluation of the proposal?
>
> +1 in intent. Specifics require further refinement. For example:
>
> Internal inconsistencies in capitalization:
> * `signalingNaN` but `isSignalingNan` and `isNan`
>
> Parameter labels, or whatever they're called now, do not reflect newly
> adopted Swift syntax in SE-0046:
> * `static func maximum(x: Self, _ y: Self) -> Self` should be `static
> func maximum(_ x: Self, _ y: Self) -> Self`, etc.
>
> Infelicitous use of prepositions to conform superficially to new
> naming guidelines:
> * `isEqual(to:)` is fine, but for consistency there's
> `isLessThanOrEqual(to:)`, which is not fine, because the preposition
> "to" applies only to "equal" and not to "less than"

That seems like a huge stretch to me. Are you claiming it's wrong to
say “x is less than or equal to y,” or are you saying something else?

--
Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jordan Rose) #19

At that point the argument label doesn’t add anything that isn’t already provided by the type.

"Otherwise, if the first argument forms part of a grammatical phrase, omit its label."

The “otherwise” comes after "When the first argument forms part of a prepositional phrase”, but in this case it forms part of two prepositional phrases.

Jordan

···

On Apr 21, 2016, at 19:58, James Berry <jberry@rogueorbit.com> wrote:

On Apr 21, 2016, at 6:13 PM, Jordan Rose via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On 'isLessThanOrEqual(to:)’: I agree with Xiaodi that the argument label is problematic here. I think the problem is that we have two prepositions that apply to the argument, and “pick the second one” leaves the base name feeling unbalanced. (Remember that we allow referring to a method by its basename alone when using it as a function value.)

Maybe: isLessThanOrEqualTo(value:) ??


(Dave Abrahams) #20

This argument makes no sense to me. A floating point number is made up
of three parts, and one part *is* not like the other ones. Furthermore,
using an enum doesn't change that one (ahem) bit.

···

on Fri Apr 22 2016, Stephen Canon <swift-evolution@swift.org> wrote:

    I’d be okay with Greg’s idea of changing the type to an enum. I’d also be
    okay with renaming this to a predicate, whatever the name ends up being.
    (“isSignBitSet”, “isSignNegative”, etc.)

Making it a predicate is weird, because then the three properties making up the
number become `isSignBitSet`, `exponent`, and `significand`; one of these things
is not like the other ones. If `signBit: Bool` were ruled out, I would rather go
with Greg’s enum proposal.

--
Dave