FloatingPoint does not conform to ExpressibleByFloatLiteral

Currently, the FloatingPoint protocol does not conform to
ExpressibleByFloatLiteral, whereas BinaryFloatingPoint does.

The only explanation I can find for this is a brief comment from Steve Canon
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160425/015691.html&gt;
during the review of SE-0067 (Enhanced Floating Point Protocols)
<https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md&gt;
:

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

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

I don’t have Steve’s level of floating-point expertise, but from a
conceptual standpoint Swift protocols encapsulate semantics and, unless I
am quite mistaken, semantically a floating-point number *can* be expressed
as a floating-point literal.

So, what exactly is the problem preventing such a conformance, and what
would be required to fix it? Do we need to revamp how numeric literals are
handled, eg. to allow arbitrary-precision integers and base-agnostic floats?

Nevin

···

On Mon, Apr 25, 2016 at 1:32 PM, Stephen Canon via swift-evolution > <swift-evolution at swift.org> wrote:

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

1 Like

Note that there are no types that ship with Swift itself that conform to
FloatingPoint but not BinaryFloatingPoint (Foundation.Decimal does not
conform to FloatingPoint, and cannot do so without some major
backwards-incompatible surgery because of how it handles subnormals,
infinity, NaN, and a host of other issues), so this discussion does not
affect most (any?) end users.

The name ExpressibleByFloatLiteral is kind of a misnomer. To conform, a
type must be able to convert a value from some other type that conforms to
_ExpressibleByBuiltinFloatLiteral, which is to say a _binary_
floating-point type.

If you create a Decimal type, it *could try to conform* to
ExpressibleByFloatLiteral, but given `var x: Double = 0.1`, the value would
actually be something like 0.10000000000000001, not exactly 0.1, because
it'd be the decimal approximation of a _binary_ approximation to the
literal. This is what Steve means by "you would change bases multiple
times," and the result is unintuitive. The alternative is to try to convert
to a decimal value via the _string representation_ of the _binary
approximation_ to the literal, which would let you recover `0.1` but lose
all information as to significant digits in the original literal (i.e.,
"0.1" vs. "0.100"), an important piece of information for Decimal types.

In other words, given the actual design of ExpressibleByFloatLiteral, it
doesn't entirely make sense for non-binary floating-point types to conform.

Yes, we can try to perform major surgery on the floating-point literal
protocol, or add another one, so that it's possible for non-binary types to
conform, and there have been proposals on this list along those lines. It
would appear to me that any such design would necessarily be more complex
and possibly slower than the existing design; at its simplest, it could
involve representing the literal as a string, which almost all types,
whether Decimal or BigInt, would expected to be able to parse anyway. But
that's a much larger discussion for another day. The answer to your
question as to why `FloatingPoint` does not refine
`ExpressibleByFloatLiteral` is as shown above; tl;dr: it doesn't make sense
to do so.

···

On Mon, Jan 15, 2018 at 4:24 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

Currently, the FloatingPoint protocol does not conform to
ExpressibleByFloatLiteral, whereas BinaryFloatingPoint does.

The only explanation I can find for this is a brief comment from Steve
Canon
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160425/015691.html&gt;
during the review of SE-0067 (Enhanced Floating Point Protocols)
<https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md&gt;
:

On Mon, Apr 25, 2016 at 1:32 PM, Stephen Canon via swift-evolution >> <swift-evolution at swift.org> wrote:

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

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

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

I don’t have Steve’s level of floating-point expertise, but from a
conceptual standpoint Swift protocols encapsulate semantics and, unless I
am quite mistaken, semantically a floating-point number *can* be expressed
as a floating-point literal.

So, what exactly is the problem preventing such a conformance, and what
would be required to fix it? Do we need to revamp how numeric literals are
handled, eg. to allow arbitrary-precision integers and base-agnostic floats?

All I’m saying is the current situation seems semantically wrong. As in,
how can a type claim to be a floating point number, if it *literally*
cannot be represented by a floating point number?

You suggest treating a float literal as a string, but another alternative
is to teach the compiler to handle arbitrary-length integer literals, and
then treat a float literal as two integer literals separated by a dot.

In any case, is this something that would have to be done before ABI
stability, or could it wait until after?

Nevin

···

On Mon, Jan 15, 2018 at 7:41 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 4:24 PM, Nevin Brackett-Rozinsky via > swift-evolution <swift-evolution@swift.org> wrote:

Currently, the FloatingPoint protocol does not conform to
ExpressibleByFloatLiteral, whereas BinaryFloatingPoint does.

The only explanation I can find for this is a brief comment from Steve
Canon
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160425/015691.html&gt;
during the review of SE-0067 (Enhanced Floating Point Protocols)
<https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md&gt;
:

On Mon, Apr 25, 2016 at 1:32 PM, Stephen Canon via swift-evolution >>> <swift-evolution at swift.org> wrote:

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

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

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

I don’t have Steve’s level of floating-point expertise, but from a
conceptual standpoint Swift protocols encapsulate semantics and, unless I
am quite mistaken, semantically a floating-point number *can* be expressed
as a floating-point literal.

So, what exactly is the problem preventing such a conformance, and what
would be required to fix it? Do we need to revamp how numeric literals are
handled, eg. to allow arbitrary-precision integers and base-agnostic floats?

Note that there are no types that ship with Swift itself that conform to
FloatingPoint but not BinaryFloatingPoint (Foundation.Decimal does not
conform to FloatingPoint, and cannot do so without some major
backwards-incompatible surgery because of how it handles subnormals,
infinity, NaN, and a host of other issues), so this discussion does not
affect most (any?) end users.

The name ExpressibleByFloatLiteral is kind of a misnomer. To conform, a
type must be able to convert a value from some other type that conforms to _
ExpressibleByBuiltinFloatLiteral, which is to say a _binary_
floating-point type.

If you create a Decimal type, it *could try to conform* to
ExpressibleByFloatLiteral, but given `var x: Double = 0.1`, the value would
actually be something like 0.10000000000000001, not exactly 0.1, because
it'd be the decimal approximation of a _binary_ approximation to the
literal. This is what Steve means by "you would change bases multiple
times," and the result is unintuitive. The alternative is to try to convert
to a decimal value via the _string representation_ of the _binary
approximation_ to the literal, which would let you recover `0.1` but lose
all information as to significant digits in the original literal (i.e.,
"0.1" vs. "0.100"), an important piece of information for Decimal types.

In other words, given the actual design of ExpressibleByFloatLiteral, it
doesn't entirely make sense for non-binary floating-point types to conform.

Yes, we can try to perform major surgery on the floating-point literal
protocol, or add another one, so that it's possible for non-binary types to
conform, and there have been proposals on this list along those lines. It
would appear to me that any such design would necessarily be more complex
and possibly slower than the existing design; at its simplest, it could
involve representing the literal as a string, which almost all types,
whether Decimal or BigInt, would expected to be able to parse anyway. But
that's a much larger discussion for another day. The answer to your
question as to why `FloatingPoint` does not refine
`ExpressibleByFloatLiteral` is as shown above; tl;dr: it doesn't make sense
to do so.

1 Like

All I’m saying is the current situation seems semantically wrong. As in,
how can a type claim to be a floating point number, if it *literally*
cannot be represented by a floating point number?

Again, you can think of it that way, but what I’m saying is that
“FloatLiteral” is a misnomer: semantically, a conforming type is
specifically claiming that it is expressible by a _binary_ floating point
number.

You suggest treating a float literal as a string, but another alternative

is to teach the compiler to handle arbitrary-length integer literals, and
then treat a float literal as two integer literals separated by a dot.

You *could*, but the fractional part is encoded quite differently from the
integral part in a binary floating point value, so that would require an
inelegant conversion.

More elegantly, you could represent the literal as a fraction, where the
denominator is given as a power of the radix. There are many ways to slice
it but this is getting far afield of your original question, which is,
essentially, that given the semantics of the two protocols it’s not correct
to conform FloatingPoint to ExpressibleByFloatLiteral, and not an oversight.

In any case, is this something that would have to be done before ABI

stability, or could it wait until after?

I don’t think the current protocol can go away, so any additions or
enhancements are something that can wait.

Nevin

···

On Mon, Jan 15, 2018 at 19:20 Nevin Brackett-Rozinsky < nevin.brackettrozinsky@gmail.com> wrote:

On Mon, Jan 15, 2018 at 7:41 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 4:24 PM, Nevin Brackett-Rozinsky via >> swift-evolution <swift-evolution@swift.org> wrote:

Currently, the FloatingPoint protocol does not conform to
ExpressibleByFloatLiteral, whereas BinaryFloatingPoint does.

The only explanation I can find for this is a brief comment from Steve
Canon
<https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160425/015691.html&gt;
during the review of SE-0067 (Enhanced Floating Point Protocols)
<https://github.com/apple/swift-evolution/blob/master/proposals/0067-floating-point-protocols.md&gt;
:

On Mon, Apr 25, 2016 at 1:32 PM, Stephen Canon via swift-evolution >>>> <swift-evolution at swift.org> wrote:

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

Any reason why FloatLiteralConvertible isn't on FloatingPoint?

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

I don’t have Steve’s level of floating-point expertise, but from a
conceptual standpoint Swift protocols encapsulate semantics and, unless I
am quite mistaken, semantically a floating-point number *can* be expressed
as a floating-point literal.

So, what exactly is the problem preventing such a conformance, and what
would be required to fix it? Do we need to revamp how numeric literals are
handled, eg. to allow arbitrary-precision integers and base-agnostic floats?

Note that there are no types that ship with Swift itself that conform to
FloatingPoint but not BinaryFloatingPoint (Foundation.Decimal does not
conform to FloatingPoint, and cannot do so without some major
backwards-incompatible surgery because of how it handles subnormals,
infinity, NaN, and a host of other issues), so this discussion does not
affect most (any?) end users.

The name ExpressibleByFloatLiteral is kind of a misnomer. To conform, a
type must be able to convert a value from some other type that conforms to
_ExpressibleByBuiltinFloatLiteral, which is to say a _binary_
floating-point type.

If you create a Decimal type, it *could try to conform* to
ExpressibleByFloatLiteral, but given `var x: Double = 0.1`, the value would
actually be something like 0.10000000000000001, not exactly 0.1, because
it'd be the decimal approximation of a _binary_ approximation to the
literal. This is what Steve means by "you would change bases multiple
times," and the result is unintuitive. The alternative is to try to convert
to a decimal value via the _string representation_ of the _binary
approximation_ to the literal, which would let you recover `0.1` but lose
all information as to significant digits in the original literal (i.e.,
"0.1" vs. "0.100"), an important piece of information for Decimal types.

In other words, given the actual design of ExpressibleByFloatLiteral, it
doesn't entirely make sense for non-binary floating-point types to conform.

Yes, we can try to perform major surgery on the floating-point literal
protocol, or add another one, so that it's possible for non-binary types to
conform, and there have been proposals on this list along those lines. It
would appear to me that any such design would necessarily be more complex
and possibly slower than the existing design; at its simplest, it could
involve representing the literal as a string, which almost all types,
whether Decimal or BigInt, would expected to be able to parse anyway. But
that's a much larger discussion for another day. The answer to your
question as to why `FloatingPoint` does not refine
`ExpressibleByFloatLiteral` is as shown above; tl;dr: it doesn't make sense
to do so.

I strongly disagree. To say that it is a “misnomer” implies that the
semantics are correct and the problem is with the name.

However, in Swift a floating point literal consists of certain patterns of
characters in source code, as specified by the language grammar. Thus it is
meaningful and correct to say that certain types can be expressed as
floating point literals. We have a protocol for exactly that purpose: to
indicate that conforming types can be written as floating point literals.

That protocol is spelled ExpressibleByFloatLiteral, which reflects the
meaning that we want and should have. The name is correct, the problem is
with the implementation.

If you want to argue that, after we fix the buggy implementation of
ExpressibleByFloatLiteral, then we should introduce a new protocol named
ExpressibleByBinaryFloatLiteral, that might be a discussion worth having.
But for the existing protocol, renaming it would not solve the underlying
issue.

Nevin

···

On Mon, Jan 15, 2018 at 8:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 19:20 Nevin Brackett-Rozinsky < > nevin.brackettrozinsky@gmail.com> wrote:

All I’m saying is the current situation seems semantically wrong. As in,
how can a type claim to be a floating point number, if it *literally*
cannot be represented by a floating point number?

Again, you can think of it that way, but what I’m saying is that
“FloatLiteral” is a misnomer: semantically, a conforming type is
specifically claiming that it is expressible by a _binary_ floating point
number.

All I’m saying is the current situation seems semantically wrong. As in,
how can a type claim to be a floating point number, if it *literally*
cannot be represented by a floating point number?

Again, you can think of it that way, but what I’m saying is that
“FloatLiteral” is a misnomer: semantically, a conforming type is
specifically claiming that it is expressible by a _binary_ floating point
number.

I strongly disagree. To say that it is a “misnomer” implies that the
semantics are correct and the problem is with the name.

However, in Swift a floating point literal consists of certain patterns of
characters in source code, as specified by the language grammar. Thus it is
meaningful and correct to say that certain types can be expressed as
floating point literals. We have a protocol for exactly that purpose: to
indicate that conforming types can be written as floating point literals.

I understand that you disagree with the current semantics, but they aren't
arbitrary. You know, I’m sure, that we can write integer literals in any of
several bases. For example, `0xffff` is an integer literal which represents
the same value as `65535`:

  0xffff == 65535 // true

It doesn’t matter in which base you write the literal, it represents some
particular _binary integer_ up to a maximum of 2048 (binary) digits.

Likewise, we can write floating-point literals in any of several bases. For
example:

  0x1.5bf0a8b145769p1 == 2.7182818284590451 // true

It doesn't matter in which base you write the literal, it represents some
particular _binary floating-point value_ up to a maximum of 63 or 52
fractional bits, depending on the platform.

That protocol is spelled ExpressibleByFloatLiteral, which reflects the

meaning that we want and should have. The name is correct, the problem is
with the implementation.

I get that you want float literals to have semantics other than what they
have. Again, that's a different conversation. I'm simply explaining that
the current semantics are not by accident, and that the implementation
isn't a buggy execution of the semantics you think they should have, but an
exact execution of semantics you may not agree with but nonetheless
deliberately chosen.

If you want to argue that, after we fix the buggy implementation of

···

On Mon, Jan 15, 2018 at 20:37 Nevin Brackett-Rozinsky < nevin.brackettrozinsky@gmail.com> wrote:

On Mon, Jan 15, 2018 at 8:51 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 19:20 Nevin Brackett-Rozinsky < >> nevin.brackettrozinsky@gmail.com> wrote:

ExpressibleByFloatLiteral, then we should introduce a new protocol named
ExpressibleByBinaryFloatLiteral, that might be a discussion worth having.
But for the existing protocol, renaming it would not solve the underlying
issue.

It is the exact conversation I started this thread to have, so if there is
any other conversation going on here then *that* is the different one. :-)

Nevin

···

On Mon, Jan 15, 2018 at 11:27 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 20:37 Nevin Brackett-Rozinsky < > nevin.brackettrozinsky@gmail.com> wrote:

That protocol is spelled ExpressibleByFloatLiteral, which reflects the
meaning that we want and should have. The name is correct, the problem is
with the implementation.

I get that you want float literals to have semantics other than what they
have. Again, that's a different conversation.

There are two distinct questions here:

- Your original questions to the list. Rephrased: Is it an implementation
bug that FloatingPoint does not refine ExpressibleByFloatLiteral? Can
FloatingPoint, with its current semantics, refine
ExpressibleByFloatLiteral, with its current semantics? The answer is: no
and no.

- Can we change the semantics? Maybe, but I doubt ExpressibleByFloatLiteral
can be outright replaced. You're not the first to wonder about how to
design an alternative protocol. Dig through the archives and you'll find
some existing ideas. My two cents: The main alternative base in question
here is 10. However, decimal storage formats and binary storage formats
share so little in common that any initializer common to both will be
extremely unwieldy for one or both formats. Personally, somewhere down the
road, I'd rather see Decimal64/128 become standard library types (already
working on it), DecimalFloatingPoint become a standard library protocol,
and `0.1` become a "decimal literal" (with Float, Double, Float80, and
Decimal64/128 all conforming) as distinct from a "float literal" that we
could then restrict to hexadecimal (?and binary) floating-point literals
(and maybe rename accordingly).

···

On Tue, Jan 16, 2018 at 00:32 Nevin Brackett-Rozinsky < nevin.brackettrozinsky@gmail.com> wrote:

On Mon, Jan 15, 2018 at 11:27 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Mon, Jan 15, 2018 at 20:37 Nevin Brackett-Rozinsky < >> nevin.brackettrozinsky@gmail.com> wrote:

That protocol is spelled ExpressibleByFloatLiteral, which reflects the
meaning that we want and should have. The name is correct, the problem is
with the implementation.

I get that you want float literals to have semantics other than what they
have. Again, that's a different conversation.

It is the exact conversation I started this thread to have, so if there is
any other conversation going on here then *that* is the different one. :-)

If we were motivated to fix this (and I’m not :-), then I think the best path forward would be to rename ExpressibleByFloatLiteral to something like ExpressibleByBinaryFloatLiteral. This would allow the introduction of a new ExpressibleByDecimalFloatLiteral with a different initializer requirement.

I’m not motivated to fix this, because there is nothing actively broken by the current state of things. With the current name we can still introduce a ExpressibleByDecimalFloatLiteral someday in the future. The two names will be a little odd, but given the cost of changing it at this point, that seems perfectly acceptable.

-Chris

···

On Jan 15, 2018, at 11:01 PM, Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

- Can we change the semantics? Maybe, but I doubt ExpressibleByFloatLiteral can be outright replaced. You're not the first to wonder about how to design an alternative protocol. Dig through the archives and you'll find some existing ideas. My two cents: The main alternative base in question here is 10. However, decimal storage formats and binary storage formats share so little in common that any initializer common to both will be extremely unwieldy for one or both formats. Personally, somewhere down the road, I'd rather see Decimal64/128 become standard library types (already working on it), DecimalFloatingPoint become a standard library protocol, and `0.1` become a "decimal literal" (with Float, Double, Float80, and Decimal64/128 all conforming) as distinct from a "float literal" that we could then restrict to hexadecimal (?and binary) floating-point literals (and maybe rename accordingly).

The thing that is “broken” here is generic programming. If I constrain
something to FloatingPoint, I cannot use a float literal in calculations
with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return 2.54 * inches // Error

}

Of course, “T: FloatingPoint” is an overconstraint in this example, as what
I actually want is “T: OrderedField” so that eg. a Rational type could be
used. And that gives a hint as to the workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return (254 / 100) * inches

}

That only works for numbers which don’t overflow the integer literals
though. If we want a really large or small value then we have to split it
in pieces:

func moles <T: FloatingPoint> (particles: T) -> T {

    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000

    return particles / avogadroNumber

}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

Nevin

···

On Tue, Jan 16, 2018 at 3:39 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jan 15, 2018, at 11:01 PM, Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
> - Can we change the semantics? Maybe, but I doubt
ExpressibleByFloatLiteral can be outright replaced. You're not the first to
wonder about how to design an alternative protocol. Dig through the
archives and you'll find some existing ideas. My two cents: The main
alternative base in question here is 10. However, decimal storage formats
and binary storage formats share so little in common that any initializer
common to both will be extremely unwieldy for one or both formats.
Personally, somewhere down the road, I'd rather see Decimal64/128 become
standard library types (already working on it), DecimalFloatingPoint become
a standard library protocol, and `0.1` become a "decimal literal" (with
Float, Double, Float80, and Decimal64/128 all conforming) as distinct from
a "float literal" that we could then restrict to hexadecimal (?and binary)
floating-point literals (and maybe rename accordingly).

If we were motivated to fix this (and I’m not :-), then I think the best
path forward would be to rename ExpressibleByFloatLiteral to something like
ExpressibleByBinaryFloatLiteral. This would allow the introduction of a
new ExpressibleByDecimalFloatLiteral with a different initializer
requirement.

I’m not motivated to fix this, because there is nothing actively broken by
the current state of things. With the current name we can still introduce
a ExpressibleByDecimalFloatLiteral someday in the future. The two names
will be a little odd, but given the cost of changing it at this point, that
seems perfectly acceptable.

-Chris

1 Like

The thing that is “broken” here is generic programming. If I constrain
something to FloatingPoint, I cannot use a float literal in calculations
with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return 2.54 * inches // Error

}

Why not constrain it to `BinaryFloatingPoint`? What other types are you
trying to use with this function?

Of course, “T: FloatingPoint” is an overconstraint in this example, as what

I actually want is “T: OrderedField”

This approach was considered during revision of integer protocols and
rejected. You could make your own protocol to which your types of interest
then conform, if you're so interested.

so that eg. a Rational type could be used. And that gives a hint as to the

workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return (254 / 100) * inches

}

Yes, you *could* do that.

That only works for numbers which don’t overflow the integer literals

though.

Integer literals don't overflow until 2048 bits. The following compiles
just fine:

func moles<T : FloatingPoint>(particles: T) -> T {
  let N_A: T = 602_214_085_774_000_000_000_000
  return particles / N_A
}

If we want a really large or small value then we have to split it in pieces:

func moles <T: FloatingPoint> (particles: T) -> T {

    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000

    return particles / avogadroNumber

}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T) ->
T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

···

On Tue, Jan 16, 2018 at 4:30 PM, Nevin Brackett-Rozinsky < nevin.brackettrozinsky@gmail.com> wrote:

On Tue, Jan 16, 2018 at 3:39 PM, Chris Lattner <clattner@nondot.org> wrote:

On Jan 15, 2018, at 11:01 PM, Xiaodi Wu via swift-evolution < >> swift-evolution@swift.org> wrote:
> - Can we change the semantics? Maybe, but I doubt
ExpressibleByFloatLiteral can be outright replaced. You're not the first to
wonder about how to design an alternative protocol. Dig through the
archives and you'll find some existing ideas. My two cents: The main
alternative base in question here is 10. However, decimal storage formats
and binary storage formats share so little in common that any initializer
common to both will be extremely unwieldy for one or both formats.
Personally, somewhere down the road, I'd rather see Decimal64/128 become
standard library types (already working on it), DecimalFloatingPoint become
a standard library protocol, and `0.1` become a "decimal literal" (with
Float, Double, Float80, and Decimal64/128 all conforming) as distinct from
a "float literal" that we could then restrict to hexadecimal (?and binary)
floating-point literals (and maybe rename accordingly).

If we were motivated to fix this (and I’m not :-), then I think the best
path forward would be to rename ExpressibleByFloatLiteral to something like
ExpressibleByBinaryFloatLiteral. This would allow the introduction of a
new ExpressibleByDecimalFloatLiteral with a different initializer
requirement.

I’m not motivated to fix this, because there is nothing actively broken
by the current state of things. With the current name we can still
introduce a ExpressibleByDecimalFloatLiteral someday in the future. The
two names will be a little odd, but given the cost of changing it at this
point, that seems perfectly acceptable.

-Chris

You're not seriously proposing this alternative, are you? I'm with Nevin on this: “let avogadroNumber: T = 6.022140857e23”.

···

On Jan 16, 2018, at 15:32 , Xiaodi Wu via swift-evolution <swift-evolution@swift.org> wrote:

On Jan 16, 2018, at 14:30 , Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

That only works for numbers which don’t overflow the integer literals though. If we want a really large or small value then we have to split it in pieces:

func moles <T: FloatingPoint> (particles: T) -> T {
    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000
    return particles / avogadroNumber
}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T) -> T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

--
Rick Mann
rmann@latencyzero.com

I'm quite serious. You'll recall above that I mentioned one possible design
for an alternative protocol is one that initializes via a string
representation of the literal. The alternative given above uses the same
functionality in a spelling that's already available. All binary
floating-point types already do support converting from a string, and any
future IEEE-compliant decimal floating-point type will necessarily have the
logic that supports it. This alternative allows Nevin to use scientific
notation as he desires, and it allows him to write generic algorithms
constrained to FloatingPoint (which, still, I don't understand why; that
protocol makes very few useful guarantees and there isn't much you can do
with it in practice--we may in fact be better off getting rid of it
altogether; in the meantime, one simple improvement would be to make
FloatingPoint refine LosslessStringConvertible, as some integer protocols
now do).

···

On Tue, Jan 16, 2018 at 5:39 PM, Rick Mann <rmann@latencyzero.com> wrote:

> On Jan 16, 2018, at 15:32 , Xiaodi Wu via swift-evolution < > swift-evolution@swift.org> wrote:
>
> On Jan 16, 2018, at 14:30 , Nevin Brackett-Rozinsky via swift-evolution < > swift-evolution@swift.org> wrote:
>>
>> That only works for numbers which don’t overflow the integer literals
though. If we want a really large or small value then we have to split it
in pieces:
>>
>> func moles <T: FloatingPoint> (particles: T) -> T {
>> let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000
>> return particles / avogadroNumber
>> }
>>
>> It would be much nicer to write “let avogadroNumber: T =
6.022140857e23”.
>>
>
> You could write:
>
> func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T)
-> T {
> let N_A = T("6.02214085774e+23")!
> return particles / N_A
> }

You're not seriously proposing this alternative, are you? I'm with Nevin
on this: “let avogadroNumber: T = 6.022140857e23”.

The thing that is “broken” here is generic programming. If I constrain
something to FloatingPoint, I cannot use a float literal in calculations
with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return 2.54 * inches // Error

}

Why not constrain it to `BinaryFloatingPoint`? What other types are you
trying to use with this function?

We should not ask nor expect people to constrain their generic algorithms
to BinaryFloatingPoint unless they are working with the radix.

so that eg. a Rational type could be used. And that gives a hint as to the

workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return (254 / 100) * inches

}

Yes, you *could* do that.

And it seems I *will* be doing that, as long as such a workaround is
necessary. Though it does appear to have the unfortunate cost of an extra
division operation.

That only works for numbers which don’t overflow the integer literals

though.

Integer literals don't overflow until 2048 bits. The following compiles
just fine:

func moles<T : FloatingPoint>(particles: T) -> T {
  let N_A: T = 602_214_085_774_000_000_000_000
  return particles / N_A
}

When I write that in a playground it shows N_A as 1.67866967797794e+18.

(Also, you appear to have mistakenly concatenated the standard uncertainty
in the last 2 digits, “74”, onto the accepted value for the constant.)

If we want a really large or small value then we have to split it in pieces:

func moles <T: FloatingPoint> (particles: T) -> T {

    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000

    return particles / avogadroNumber

}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T)
-> T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

…or I could write “T: FloatingPoint & ExpressibleByFloatLiteral”. I could
even make a typealias for that. But I shouldn’t have to.

Nevin

···

On Tue, Jan 16, 2018 at 6:31 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 16, 2018 at 4:30 PM, Nevin Brackett-Rozinsky < > nevin.brackettrozinsky@gmail.com> wrote:

The thing that is “broken” here is generic programming. If I constrain
something to FloatingPoint, I cannot use a float literal in calculations
with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return 2.54 * inches // Error

}

Why not constrain it to `BinaryFloatingPoint`? What other types are you
trying to use with this function?

We should not ask nor expect people to constrain their generic algorithms
to BinaryFloatingPoint unless they are working with the radix.

On the contrary, I think the "currency" floating-point protocol should be
`BinaryFloatingPoint`. Having worked with `FloatingPoint` rather
extensively (or, attempted to at least), I reiterate that I find it to be a
distinctly unhelpful protocol in terms of enabling useful generic
algorithms; if we were to do anything, I'd advocate for its complete
removal before ABI stability sets in. For compatibility we would supply a
deprecated `typealias FloatingPoint = BinaryFloatingPoint`.

so that eg. a Rational type could be used. And that gives a hint as to

the workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {

    return (254 / 100) * inches

}

Yes, you *could* do that.

And it seems I *will* be doing that, as long as such a workaround is
necessary. Though it does appear to have the unfortunate cost of an extra
division operation.

There should be no runtime cost when specialized.

That only works for numbers which don’t overflow the integer literals

though.

Integer literals don't overflow until 2048 bits. The following compiles
just fine:

func moles<T : FloatingPoint>(particles: T) -> T {
  let N_A: T = 602_214_085_774_000_000_000_000
  return particles / N_A
}

When I write that in a playground it shows N_A as 1.67866967797794e+18.

(Also, you appear to have mistakenly concatenated the standard uncertainty
in the last 2 digits, “74”, onto the accepted value for the constant.)

Yes, those are typos; I was typing freehand into an email. Please adjust
the number and value of digits as necessary.

If we want a really large or small value then we have to split it in

pieces:

func moles <T: FloatingPoint> (particles: T) -> T {

    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000

    return particles / avogadroNumber

}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T)
-> T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

…or I could write “T: FloatingPoint & ExpressibleByFloatLiteral”. I could
even make a typealias for that. But I shouldn’t have to.

Sure, you could do that too. Your solution here is actually probably the
best, in that if a user wishes to conform a decimal type
to ExpressibleByFloatLiteral, your extensions would also work for that
type. I've already explained why the semantics of ExpressibleByFloatLiteral
are such that not every FloatingPoint type could conform.

···

On Tue, Jan 16, 2018 at 6:20 PM, Nevin Brackett-Rozinsky < nevin.brackettrozinsky@gmail.com> wrote:

On Tue, Jan 16, 2018 at 6:31 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 16, 2018 at 4:30 PM, Nevin Brackett-Rozinsky < >> nevin.brackettrozinsky@gmail.com> wrote:

I’m with Nevin on this one. Perhaps the easiest thing to do is to add something to the FloatLiteral type that lets you get it as a string if desired.

Didn’t we have a discussion a while back on how to make Integer Literals work with BigInt? Maybe there is an idea from that discussion that would help.

Tl;dr: Literals shouldn’t be tied to a particular implementation of a single conforming type (though they can/should be optimized for common implementations). The issue here is that FloatLiteral is throwing out information which is given to it based on its underlying implementation. I view this as a bug.

Thanks,
Jon

···

On Jan 16, 2018, at 4:20 PM, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> wrote:

On Tue, Jan 16, 2018 at 6:31 PM, Xiaodi Wu <xiaodi.wu@gmail.com <mailto:xiaodi.wu@gmail.com>> wrote:
On Tue, Jan 16, 2018 at 4:30 PM, Nevin Brackett-Rozinsky <nevin.brackettrozinsky@gmail.com <mailto:nevin.brackettrozinsky@gmail.com>> wrote:
The thing that is “broken” here is generic programming. If I constrain something to FloatingPoint, I cannot use a float literal in calculations with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {
    return 2.54 * inches // Error
}

Why not constrain it to `BinaryFloatingPoint`? What other types are you trying to use with this function?

We should not ask nor expect people to constrain their generic algorithms to BinaryFloatingPoint unless they are working with the radix.

so that eg. a Rational type could be used. And that gives a hint as to the workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {
    return (254 / 100) * inches
}

Yes, you *could* do that.

And it seems I *will* be doing that, as long as such a workaround is necessary. Though it does appear to have the unfortunate cost of an extra division operation.

That only works for numbers which don’t overflow the integer literals though.

Integer literals don't overflow until 2048 bits. The following compiles just fine:

func moles<T : FloatingPoint>(particles: T) -> T {
  let N_A: T = 602_214_085_774_000_000_000_000
  return particles / N_A
}

When I write that in a playground it shows N_A as 1.67866967797794e+18.

(Also, you appear to have mistakenly concatenated the standard uncertainty in the last 2 digits, “74”, onto the accepted value for the constant.)

If we want a really large or small value then we have to split it in pieces:

func moles <T: FloatingPoint> (particles: T) -> T {
    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000
    return particles / avogadroNumber
}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T) -> T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

…or I could write “T: FloatingPoint & ExpressibleByFloatLiteral”. I could even make a typealias for that. But I shouldn’t have to.

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

1 Like

I’m with Nevin on this one. Perhaps the easiest thing to do is to add
something to the FloatLiteral type that lets you get it as a string if
desired.

Didn’t we have a discussion a while back on how to make Integer Literals
work with BigInt? Maybe there is an idea from that discussion that would
help.

Tl;dr: Literals shouldn’t be tied to a particular implementation of a
single conforming type (though they can/should be optimized for common
implementations). The issue here is that FloatLiteral is throwing out
information which is given to it based on its underlying implementation. I
view this as a bug.

I suppose you can view it as anything you want; but the initial question
was whether there is a reason that FloatingPoint doesn't conform to
ExpressibleByFloatLiteral, and there very much is: namely, the current
semantics of a float literal.

Literals aren't tied to any particular conforming type, but they are always
tied to some group of built-in types. Until there's a clear additional use
case (e.g., a Decimal type), it's pretty pointless to redesign literal
protocols, because whether or not a particular way in which information
about the literal value is conveyed to the initializer is ergonomic and
efficient will depend on the underlying implementation of the type.

BigInt will be able to take advantage, in the next version of Swift, to
DoubleWidth's conformance to _ExpressibleByBuiltinIntegerLiteral; in other
words, literals of up to 2048 bits are supported, and that's plenty for a
literal.

···

On Wed, Jan 17, 2018 at 4:56 PM, Jonathan Hull via swift-evolution < swift-evolution@swift.org> wrote:

Thanks,
Jon

On Jan 16, 2018, at 4:20 PM, Nevin Brackett-Rozinsky via swift-evolution < > swift-evolution@swift.org> wrote:

On Tue, Jan 16, 2018 at 6:31 PM, Xiaodi Wu <xiaodi.wu@gmail.com> wrote:

On Tue, Jan 16, 2018 at 4:30 PM, Nevin Brackett-Rozinsky < >> nevin.brackettrozinsky@gmail.com> wrote:

The thing that is “broken” here is generic programming. If I constrain
something to FloatingPoint, I cannot use a float literal in calculations
with it:

func centimeters<T: FloatingPoint> (inches: T) -> T {
    return 2.54 * inches // Error

}

Why not constrain it to `BinaryFloatingPoint`? What other types are you
trying to use with this function?

We should not ask nor expect people to constrain their generic algorithms
to BinaryFloatingPoint unless they are working with the radix.

so that eg. a Rational type could be used. And that gives a hint as to

the workaround:

func centimeters<T: FloatingPoint> (inches: T) -> T {
    return (254 / 100) * inches
}

Yes, you *could* do that.

And it seems I *will* be doing that, as long as such a workaround is
necessary. Though it does appear to have the unfortunate cost of an extra
division operation.

That only works for numbers which don’t overflow the integer literals

though.

Integer literals don't overflow until 2048 bits. The following compiles
just fine:

func moles<T : FloatingPoint>(particles: T) -> T {
  let N_A: T = 602_214_085_774_000_000_000_000
  return particles / N_A
}

When I write that in a playground it shows N_A as 1.67866967797794e+18.

(Also, you appear to have mistakenly concatenated the standard uncertainty
in the last 2 digits, “74”, onto the accepted value for the constant.)

If we want a really large or small value then we have to split it in

pieces:

func moles <T: FloatingPoint> (particles: T) -> T {
    let avogadroNumber: T = 6_022_140_857 * 100_000_000_000_000
    return particles / avogadroNumber

}

It would be much nicer to write “let avogadroNumber: T = 6.022140857e23”.

You could write:

func moles<T : FloatingPoint & LosslessStringConvertible>(particles: T)
-> T {
  let N_A = T("6.02214085774e+23")!
  return particles / N_A
}

…or I could write “T: FloatingPoint & ExpressibleByFloatLiteral”. I could
even make a typealias for that. But I shouldn’t have to.

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

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