[swift-evolution] SE-0170: NSNumber bridging and Numeric types

Xiaodi Wu xiaodi.wu at gmail.com
Wed Apr 19 20:09:08 CDT 2017


On Wed, Apr 19, 2017 at 6:35 PM, Philippe Hausler <phausler at apple.com>
wrote:

>
>
> On Apr 19, 2017, at 16:17, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>
> On Wed, Apr 19, 2017 at 6:00 PM, Philippe Hausler <phausler at apple.com> w
> rote:
>
>>
>> On Apr 19, 2017, at 3:23 PM, Xiaodi Wu <xiaodi.wu at gmail.com> wrote:
>>
>> On Wed, Apr 19, 2017 at 3:19 PM, Martin R <martinr448 at gmail.com> wrote:
>>
>>> On 19. Apr 2017, at 01:48, Xiaodi Wu <xiaodi.wu at gmail.com> 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?

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 :)


> 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 <phausler at apple.com>
>>> wrote:
>>>
>>>>
>>>> On Apr 18, 2017, at 9:22 AM, Stephen Canon <scanon at apple.com> wrote:
>>>>
>>>>
>>>> On Apr 18, 2017, at 12:17 PM, Joe Groff <jgroff at apple.com> wrote:
>>>>
>>>>
>>>> On Apr 17, 2017, at 5:56 PM, Xiaodi Wu via swift-evolution <
>>>> swift-evolution at swift.org> 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 <
>>>> swift-evolution at swift.org> 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
>>>>
>>>> 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
>>>
>>>     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.
>>>
>>>
>>> (+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
>>>>
>>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170419/9a86ade9/attachment.html>


More information about the swift-evolution mailing list