12

采取以下代码:

NSError *error;
NSString *myJSONString = @"{ \"foo\" : 0.1}";
NSData *jsonData = [myJSONString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *results = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];

我的问题是,是results[@"foo"]NSDecimalNumber,还是具有有限二进制精度的东西,如 double 或 float?基本上,我有一个应用程序需要带有 的无损准确性NSDecimalNumber,并且需要确保 JSON 反序列化不会因为双精度/浮点数等而导致舍入。

例如,如果它被解释为浮点数,我会精确地遇到这样的问题:

float baz = 0.1;
NSLog(@"baz: %.20f", baz);
// prints baz: 0.10000000149011611938

我尝试将其解释foo为 NSDecimalNumber 并打印结果:

NSDecimalNumber *fooAsDecimal = results[@"foo"];
NSLog(@"fooAsDecimal: %@", [fooAsDecimal stringValue]);
// prints fooAsDecimal: 0.1

但后来我发现调用stringValueanNSDecimalNumber并不会打印所有有效数字,例如..

NSDecimalNumber *barDecimal = [NSDecimalNumber decimalNumberWithString:@"0.1000000000000000000000000000000000000000000011"];
NSLog(@"barDecimal: %@", barDecimal);
// prints barDecimal: 0.1

...所以打印fooAsDecimal并不能告诉我results[@"foo"]JSON 解析器是否在某个时候四舍五入到有限精度。

明确地说,我意识到我可以在 JSON 表示中使用字符串而不是数字来存储 foo 的值,即"0.1"代替0.1,然后使用[NSDecimalNumber decimalNumberWithString:results[@"foo"]]。但是,我感兴趣的是 NSJSONSerialization 类如何反序列化 JSON 数字,所以我知道这是否真的有必要。

4

4 回答 4

7

NSJSONSerializationJSONSerialization在 Swift 中)遵循一般模式:

  1. 如果一个数字只有一个整数部分(没有小数或指数),请尝试将其解析为long long. 如果没有溢出,返回一个NSNumberwith long long
  2. 尝试使用 解析双精度strtod_l。如果它没有溢出,返回一个NSNumberwith double
  3. 在所有其他情况下,尝试使用NSDecimalNumber它支持更大范围的值,特别是尾数最多 38 位和 -128...127 之间的指数。

如果您查看人们发布的其他示例,您会发现当值超出 a 的范围或精度时,double您会得到NSDecimalNumber回报。

于 2016-09-18T02:58:41.293 回答
4

简短的回答是,如果您需要 NSDecimalNumber 级别的精度,则不应序列化为 JSON。JSON 只有一种数字格式:double,其精度不如 NSDecimalNumber。

仅具有学术兴趣的长答案是“不一定”,因为短答案也是正确答案。NSJSONSerialization 有时会反序列化为 NSDecimalNumber,但它没有记录,而且我还没有确定它在什么情况下执行。例如:

    BOOL boolYes = YES;
    int16_t int16 = 12345;
    int32_t int32 = 2134567890;
    uint32_t uint32 = 3124141341;
    unsigned long long ull = 312414134131241413ull;
    double dlrep = 1.5;
    double dlmayrep = 1.1234567891011127;
    float fl = 3124134134678.13;
    double dl = 13421331.72348729 * 1000000000000000000000000000000000000000000000000000.0;
    long long negLong = -632414314135135234;
    unsigned long long unrepresentable = 10765432100123456789ull;

    dict[@"bool"] = @(boolYes);
    dict[@"int16"] = @(int16);
    dict[@"int32"] = @(int32);
    dict[@"dlrep"] = @(dlrep);
    dict[@"dlmayrep"] = @(dlmayrep);
    dict[@"fl"] = @(fl);
    dict[@"dl"] = @(dl);
    dict[@"uint32"] = @(uint32);
    dict[@"ull"] = @(ull);
    dict[@"negLong"] = @(negLong);
    dict[@"unrepresentable"] = @(unrepresentable);

    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted error:nil];

    NSDictionary *dict_back = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];

并在调试器中:

(lldb) po [dict_back[@"bool"] class]
__NSCFBoolean
(lldb) po [dict_back[@"int16"] class]
__NSCFNumber
(lldb) po [dict_back[@"int32"] class]
__NSCFNumber
(lldb) po [dict_back[@"ull"] class]
__NSCFNumber
(lldb) po [dict_back[@"fl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dl"] class]
NSDecimalNumber
(lldb) po [dict_back[@"dlrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"dlmayrep"] class]
__NSCFNumber
(lldb) po [dict_back[@"negLong"] class]
__NSCFNumber
(lldb) po [dict_back[@"unrepresentable"] class]
NSDecimalNumber

所以,随心所欲。你绝对不应该假设如果你将一个 NSDecimalNumber 序列化为 JSON,你会得到一个 NSDecimalNumber。

但是,同样,您不应该将 NSDecimalNumbers 存储在 JSON 中。

于 2014-01-27T16:27:23.230 回答
2

我遇到了同样的问题,除了我使用的是 Swift 3。我制作了 JSONSerialization 类的补丁版本,它将所有数字解析为Decimal's。它只能解析/反序列化 JSON,但没有任何序列化代码。它基于 Apple 在 Swift 中对 Foundation 的开源重新实现。

于 2017-02-26T18:10:58.197 回答
1

回答标题中的问题:不,它没有,它创建了NSNumber对象。您可以轻松地对此进行测试:

NSArray *a = @[[NSDecimalNumber decimalNumberWithString:@"0.1"]];
NSData *data = [NSJSONSerialization dataWithJSONObject:a options:0 error:NULL];
a = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@", [a[0] class]);

将打印__NSCFNumber

您可以将该NSNumber对象转换为NSDecimalNumberwith [NSDecimalNumber decimalNumberWithDecimal:[number decimalValue]],但根据文档decimalValue

不保证返回的值对于 float 和 double 值是准确的。

于 2013-08-01T06:43:41.537 回答