So is it correct to say that for all types T which NSNumber can hold (Double,
Float, Int, UInt, ... )
T(exactly: someNSNumber)
will succeed if and only if
NSNumber(value: T(truncating: someNSNumber)) == someNSNumber
holds?
> On 20. Apr 2017, at 18:10, Philippe Hausler <[email protected]> wrote:
>
>>
>> On Apr 19, 2017, at 6:09 PM, Xiaodi Wu <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>>
>>
>> On Wed, Apr 19, 2017 at 6:35 PM, Philippe Hausler <[email protected]
>> <mailto:[email protected]>> wrote:
>>
>>
>>> On Apr 19, 2017, at 16:17, Xiaodi Wu <[email protected]
>>> <mailto:[email protected]>> wrote:
>>>
>>> On Wed, Apr 19, 2017 at 6:00 PM, Philippe Hausler <[email protected]
>>> <mailto:[email protected]>> wrote:
>>>
>>>> On Apr 19, 2017, at 3:23 PM, Xiaodi Wu <[email protected]
>>>> <mailto:[email protected]>> wrote:
>>>>
>>>> On Wed, Apr 19, 2017 at 3:19 PM, Martin R <[email protected]
>>>> <mailto:[email protected]>> wrote:
>>>>> On 19. Apr 2017, at 01:48, Xiaodi Wu <[email protected]
>>>>> <mailto:[email protected]>> wrote:
>>>>>
>>>>> So, as I understand it, `Float.init(exactly: Double.pi) == nil`. I would
>>>>> expect NSNumber to behave similarly (a notion with which Martin
>>>>> disagrees, I guess). I don't see a test that shows whether NSNumber
>>>>> behaves or does not behave in that way.
>>>>
>>>> At present they behave differently:
>>>>
>>>> print(Float(exactly: Double.pi) as Any)
>>>> // nil
>>>> print(Float(exactly: NSNumber(value: Double.pi)) as Any)
>>>> // Optional(3.14159274)
>>>>
>>>> I realize that identical behavior would be logical and least surprising.
>>>> My only concern was about cases like
>>>>
>>>> let num = ... // some NSNumber from a JSON deserialization
>>>> let fval = Float(exactly: num)
>>>>
>>>> where one cannot know how the number is represented internally and what
>>>> precision it needs. But then one could use the truncating conversion or
>>>> `.floatValue` instead.
>>>>
>>>> JSON numbers are double-precision floating point, unless I'm
>>>> misunderstanding something. If someone writes `Float(exactly:
>>>> valueParsedFromJSON)`, surely, that can only mean that they *really,
>>>> really* prefer nil over an imprecise value. I can see no other reason to
>>>> insist on using both Float and .init(exactly:).
>>>
>>> JSON does not claim 32 or 64 bit floating point, or for that matter 128 or
>>> infinite bit floating point :(
>>>
>>>
>>> Oops, you're right. I see they've wanted to future-proof this. That said,
>>> RFC 7159 *does* say:
>>>
>>> This specification allows implementations to set limits on the range
>>> and precision of numbers accepted. Since software that implements
>>> IEEE 754-2008 binary64 (double precision) numbers [IEEE754] is
>>> generally available and widely used, good interoperability can be
>>> achieved by implementations that expect no more precision or range
>>> than these provide, in the sense that implementations will
>>> approximate JSON numbers within the expected precision.
>>>
>>> So JSON doesn't set limits on how numbers are represented, but JSON
>>> implementations are permitted to (and I'd imagine that all in fact do). A
>>> user of a JSON deserialization library can rightly expect to know the
>>> numeric limits of that implementation; for the purposes of bridging
>>> NSNumber, if the answer is that the implementation parses JSON numbers as
>>> double-precision values, Double(exactly:) would be the right choice;
>>> otherwise, if it's 80-bit values, then Float80(exactly:) would be the right
>>> choice, etc.
>>>
>>
>> Float80 is not compatible with NSNumber; and is well out of scope for this
>> proposal.
>>
>> OK, so Double is the largest floating point type compatible with NSNumber?
>> It stands to reason that any Swift JSON implementation that uses NSNumber
>> for parsed floating point values would at most have that much range and
>> precision, right?
>
> For JSONSerialization (which I am most familiar with and ships with
> Foundation); it can emit both NSNumbers and NSDecimalNumber. A rough
> approximation of the behavior: if it can store the value in an integer type
> it stores it as such in a NSNumber (iirc up to UINT64_MAX) and then if it has
> a decimal point it will attempt to parse as a double but if that is not
> enough storage it will store the best possible value into NSDecimalNumber.
>
> So NSNumber itself (excluding subclasses) can only store up to a 64 bit
> value.
>
>>
>> If so, then every floating point value parsed by any such Swift JSON
>> implementation would be exactly representable as a Double: regardless of
>> whether that specific implementation uses Float or Double under the hood,
>> every Float can be represented exactly as a Double. If a user is trying to
>> bridge such a NSNumber instance specifically to *Float* instead of Double,
>> and they are asking for an exact value, there's no a priori reason to think
>> that this user would be more likely to care only about the range and not the
>> precision, or vice versa. Which is to say, I don't think you'll get too many
>> bug reports :)
>
> In my mind there are two considerations here; balance against the surprise
> from new developers learning their first programming language versus
> consistency. In the end even if I believe the behavior is sub-par I would
> rather it be consistent. Primarily consistency is easier to teach even if it
> is derived from a standard developed with legacy behavior of C at its heart.
>
> Perhaps in the future we might want to eventually allow conversions to and
> from NSNumber via the Integer and FloatingPoint protocols; however I would
> guess that there needs to be a lot more thought and perhaps some
> modifications there to pull that off. Not to sound like a broken record, but
> again that it is out of scope for right now.
>
>>
>>>
>>> After thinking about it more; it seems reasonable to restrict it to the
>>> behavior of Float(exactly: Double(…)). I am certain this will probably in
>>> the end cause more bugs for me to have to address and mark as “behaves
>>> correctly” and confuse a few new developers - but in the end they chose
>>> Swift and the consistent story would be the current behavior of
>>> Float(exactly: Double).
>>>
>>>>
>>>>>
>>>>>
>>>>> On Tue, Apr 18, 2017 at 11:43 AM, Philippe Hausler <[email protected]
>>>>> <mailto:[email protected]>> wrote:
>>>>>
>>>>>> On Apr 18, 2017, at 9:22 AM, Stephen Canon <[email protected]
>>>>>> <mailto:[email protected]>> wrote:
>>>>>>
>>>>>>>
>>>>>>> On Apr 18, 2017, at 12:17 PM, Joe Groff <[email protected]
>>>>>>> <mailto:[email protected]>> wrote:
>>>>>>>
>>>>>>>
>>>>>>>> On Apr 17, 2017, at 5:56 PM, Xiaodi Wu via swift-evolution
>>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>>
>>>>>>>> It seems Float.init(exactly: NSNumber) has not been updated to behave
>>>>>>>> similarly?
>>>>>>>>
>>>>>>>> I would have to say, I would naively expect "exactly" to behave
>>>>>>>> exactly as it says, exactly. I don't think it should be a synonym for
>>>>>>>> Float(Double(exactly:)).
>>>>>>>> On Mon, Apr 17, 2017 at 19:24 Philippe Hausler via swift-evolution
>>>>>>>> <[email protected] <mailto:[email protected]>> wrote:
>>>>>>>> I posted my branch and fixed up the Double case to account for your
>>>>>>>> concerns (with a few inspired unit tests to validate)
>>>>>>>>
>>>>>>>> https://github.com/phausler/swift/tree/safe_nsnumber
>>>>>>>> <https://github.com/phausler/swift/tree/safe_nsnumber>
>>>>>>>>
>>>>>>>> There is a builtin assumption here though: it does presume that the
>>>>>>>> swift’s representation of Double and Float are IEEE compliant. However
>>>>>>>> that is a fairly reasonable assumption in the tests.
>>>>>>>
>>>>
>>>>
>>>> Even with the updated code at
>>>> https://github.com/phausler/swift/tree/safe_nsnumber
>>>> <https://github.com/phausler/swift/tree/safe_nsnumber>
>>>>
>>>> print(Double(exactly: NSNumber(value: Int64(9000000000000000001))) as
>>>> Any)
>>>> // Optional(9e+18)
>>>>
>>>> still succeeds, however the reason seems to be an error in the
>>>> `init(exactly value: someIntegerType)` inititializers of Float/Double, I
>>>> have submitted a bug report: https://bugs.swift.org/browse/SR-4634
>>>> <https://bugs.swift.org/browse/SR-4634>.
>>>>
>>>>
>>>>>>> (+Steve Canon) What is the behavior of Float.init(exactly: Double)?
>>>>>>> NSNumber's behavior would ideally be consistent with that.
>>>>>>
>>>>>> The implementation is essentially just:
>>>>>>
>>>>>> self.init(other)
>>>>>> guard Double(self) == other else {
>>>>>> return nil
>>>>>> }
>>>>>>
>>>>>> i.e. if the result is not equal to the source when round-tripped back to
>>>>>> double (which is always exact), the result is nil.
>>>>>>
>>>>>> – Steve
>>>>>
>>>>> Pretty much the same trick inside of CFNumber/NSNumber
_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution