Унифицирует ли NSJSONSerialization числа как NSDecimalNumber?

Возьмите следующий фрагмент кода:

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 и т.д.

например. если он был интерпретирован как float, я бы столкнулся с такими проблемами:

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

Но потом я обнаружил, что вызов stringValue на NSDecimalNumber не печатает все значимые цифры в любом случае, например...

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, поэтому я знаю, действительно ли это необходимо или нет.

Ответ 1

NSJSONSerializationJSONSerialization в Swift) следуют общей схеме:

  • Если число имеет только целую часть (без десятичной или экспоненты), попытайтесь проанализировать ее как long long. Если это не переполнение, верните NSNumber с помощью long long.
  • Попытка разобрать двойную с strtod_l. Если он не переполняется, верните NSNumber с помощью double.
  • Во всех остальных случаях попытайтесь использовать NSDecimalNumber, который поддерживает гораздо больший диапазон значений, в частности мантисса до 38 цифр и показатель между -128... 127.

Если вы посмотрите на другие примеры, опубликованные людьми, вы можете увидеть, что, когда значение превышает диапазон или точность double, вы получаете обратно NSDecimalNumber.

Ответ 2

Короткий ответ заключается в том, что вы не должны сериализоваться в JSON, если вам требуются уровни точности NSDecimalNumber. 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.

Ответ 3

Чтобы ответить на вопрос в заголовке: Нет, он не создает объекты 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 в NSDecimalNumber с помощью [NSDecimalNumber decimalNumberWithDecimal:[number decimalValue]], но в соответствии с документами для decimalValue

Возвращаемое значение не гарантируется для точных значений float и double.

Ответ 4

У меня была та же проблема, за исключением того, что я использовал Swift 3. Я сделал исправленную версию класса JSONSerialization, которая анализирует все числа как Decimal. Он может только анализировать/десериализовать JSON, но не имеет никакого кода сериализации. Это основано на повторной реализации Apple с открытым исходным кодом Фонда в Swift.