On 5/9/16 14:01, Joe Groff via swift-evolution wrote: > >> On May 8, 2016, at 10:30 PM, Morten Bek Ditlevsen <[email protected]> wrote: >> >> This would be an excellent solution to the issue. >> Do you know if there are any existing plans for something like the >> DecimalLiteralConvertible? > > Not that I know of. Someone would have to submit a proposal. > >> >> Another thought: >> Would it make sense to have the compiler warn about float literal precision >> issues? >> Initialization of two different variables with the exact same literal value >> could yield different precision results if one had a FloatLiteralType >> aliased to Float80 and the other aliased to Float. > > That's definitely a possibility. We already have machinery in place to raise > errors when integer literals overflow Int* types, and we could do something > similar for float literals that have excessive precision.
For compilation, it would probably be overkill to show even a warning for non-representable numbers like 0.1 assigned to a binary floating-point type, but perhaps such a warning might be acceptable in a playground? >> On Fri, May 6, 2016 at 6:46 PM Joe Groff <[email protected]> wrote: >> >>> On May 6, 2016, at 9:42 AM, Stephen Canon <[email protected]> wrote: >>> >>> >>>> On May 6, 2016, at 12:41 PM, Joe Groff via swift-evolution >>>> <[email protected]> wrote: >>>> >>>>> >>>>> On May 6, 2016, at 2:24 AM, Morten Bek Ditlevsen via swift-evolution >>>>> <[email protected]> wrote: >>>>> >>>>> Currently, in order to conform to FloatLiteralConvertible you need to >>>>> implement >>>>> an initializer accepting a floatLiteral of the typealias: >>>>> FloatLiteralType. >>>>> However, this typealias can only be Double, Float, Float80 and other >>>>> built-in >>>>> floating point types (to be honest, I do not know the exact limitation >>>>> since I have >>>>> not been able to read find this in the documentation). >>>>> >>>>> These floating point types have precision limitations that are not >>>>> necessarily >>>>> present in the type that you are making FloatLiteralConvertible. >>>>> >>>>> Let’s imagine a CurrencyAmount type that uses an NSDecimalNumber as the >>>>> representation of the value: >>>>> >>>>> >>>>> public struct CurrencyAmount { >>>>> public let value: NSDecimalNumber >>>>> // .. other important currency-related stuff .. >>>>> } >>>>> >>>>> extension CurrencyAmount: FloatLiteralConvertible { >>>>> public typealias FloatLiteralType = Double >>>>> >>>>> public init(floatLiteral amount: FloatLiteralType) { >>>>> print(amount.debugDescription) >>>>> value = NSDecimalNumber(double: amount) >>>>> } >>>>> } >>>>> >>>>> let a: CurrencyAmount = 99.99 >>>>> >>>>> >>>>> The printed value inside the initializer is 99.989999999999995 - so the >>>>> value >>>>> has lost precision already in the intermediary Double representation. >>>>> >>>>> I know that there is also an issue with the NSDecimalNumber double >>>>> initializer, >>>>> but this is not the issue that we are seeing here. >>>>> >>>>> >>>>> One suggestion for a solution to this issue would be to allow the >>>>> FloatLiteralType to be aliased to a String. In this case the compiler >>>>> should >>>>> parse the float literal token: 99.99 to a String and use that as input >>>>> for the >>>>> FloatLiteralConvertible initializer. >>>>> >>>>> This would mean that arbitrary literal precisions are allowed for >>>>> FloatLiteralConvertibles that implement their own parsing of a String >>>>> value. >>>>> >>>>> For instance, if the CurrencyAmount used a FloatLiteralType aliased to >>>>> String we >>>>> would have: >>>>> >>>>> extension CurrencyAmount: FloatLiteralConvertible { >>>>> public typealias FloatLiteralType = String >>>>> >>>>> public init(floatLiteral amount: FloatLiteralType) { >>>>> value = NSDecimalNumber(string: amount) >>>>> } >>>>> } >>>>> >>>>> and the precision would be the same as creating an NSDecimalNumber from a >>>>> String: >>>>> >>>>> let a: CurrencyAmount = 1.00000000000000000000000000000000001 >>>>> >>>>> print(a.value.debugDescription) >>>>> >>>>> Would give: 1.00000000000000000000000000000000001 >>>>> >>>>> >>>>> How does that sound? Is it completely irrational to allow the use of >>>>> Strings as >>>>> the intermediary representation of float literals? >>>>> I think that it makes good sense, since it allows for arbitrary precision. >>>>> >>>>> Please let me know what you think. >>>> >>>> Like Dmitri said, a String is not a particularly efficient intermediate >>>> representation. For common machine numeric types, we want it to be >>>> straightforward for the compiler to constant-fold literals down to >>>> constants in the resulting binary. For floating-point literals, I think we >>>> could achieve this by changing the protocol to "deconstruct" the literal >>>> value into integer significand and exponent, something like this: >>>> >>>> // A type that can be initialized from a decimal literal such as >>>> // `1.1` or `2.3e5`. >>>> protocol DecimalLiteralConvertible { >>>> // The integer type used to represent the significand and exponent of the >>>> value. >>>> typealias Component: IntegerLiteralConvertible >>>> >>>> // Construct a value equal to `decimalSignificand * 10**decimalExponent`. >>>> init(decimalSignificand: Component, decimalExponent: Component) >>>> } >>>> >>>> // A type that can be initialized from a hexadecimal floating point >>>> // literal, such as `0x1.8p-2`. >>>> protocol HexFloatLiteralConvertible { >>>> // The integer type used to represent the significand and exponent of the >>>> value. >>>> typealias Component: IntegerLiteralConvertible >>>> >>>> // Construct a value equal to `hexadecimalSignificand * >>>> 2**binaryExponent`. >>>> init(hexadecimalSignificand: Component, binaryExponent: Component) >>>> } >>>> >>>> Literals would desugar to constructor calls as follows: >>>> >>>> 1.0 // T(decimalSignificand: 1, decimalExponent: 0) >>>> 0.123 // T(decimalSignificand: 123, decimalExponent: -3) >>>> 1.23e-2 // same >>>> >>>> 0x1.8p-2 // T(hexadecimalSignificand: 0x18, binaryExponent: -6) >>> >>> This seems like a very good approach to me. >> >> It occurs to me that "sign" probably needs to be an independent parameter, >> to be able to accurately capture literal -0 and 0: >> >> // A type that can be initialized from a decimal literal such as >> // `1.1` or `-2.3e5`. >> protocol DecimalLiteralConvertible { >> // The integer type used to represent the significand and exponent of the >> value. >> typealias Component: IntegerLiteralConvertible >> >> // Construct a value equal to `decimalSignificand * 10**decimalExponent * >> (isNegative ? -1 : 1)`. >> init(decimalSignificand: Component, decimalExponent: Component, isNegative: >> Bool) >> } >> >> // A type that can be initialized from a hexadecimal floating point >> // literal, such as `0x1.8p-2`. >> protocol HexFloatLiteralConvertible { >> // The integer type used to represent the significand and exponent of the >> value. >> typealias Component: IntegerLiteralConvertible >> >> // Construct a value equal to `hexadecimalSignificand * 2**binaryExponent * >> (isNegative ? -1 : 1)`. >> init(hexadecimalSignificand: Component, binaryExponent: Component, >> isNegative: Bool) >> } >> >> -Joe > > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution > -- Rainer Brockerhoff <[email protected]> Belo Horizonte, Brazil "In the affairs of others even fools are wise In their own business even sages err." http://brockerhoff.net/blog/ _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
