2013-08-01 9 views
11

Nehmen Sie das folgende Stück Code:Sortiert NSJSONSerialization Nummern als NSDecimalNumber?

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

Meine Frage ist, ist results[@"foo"] ein NSDecimalNumber, oder etwas mit endlicher binärer Präzision wie ein Doppel- oder schwimmen? Im Grunde habe ich eine Anwendung, die die verlustfreie Genauigkeit erfordert, die mit einer NSDecimalNumber kommt, und muss sicherstellen, dass die JSON-Deserialisierung nicht zu Rundungen aufgrund von Doubles/Floats usw. führt.

z. wenn es als Schwimmer interpretiert wurde, würde ich in Probleme wie diese mit Präzision auszuführen:

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

Ich habe versucht, foo als NSDecimalNumber zu interpretieren und das Drucken des Sende:

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

Aber dann habe ich festgestellt, dass alle signifikanten Stellen druckt nicht ohnehin auf einem NSDecimalNumberstringValue Aufruf, zB ..

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

... soDrucksagt mir nicht, ob results[@"foo"] zu irgendeinem Zeitpunkt durch den JSON-Parser auf endliche Genauigkeit gerundet wurde oder nicht.

klar sein, wird mir klar, ich eher einen String verwenden könnte als eine Zahl in Vertretung der JSON den Wert von foo zu speichern, das heißt "0.1" statt 0.1, und verwenden Sie dann [NSDecimalNumber decimalNumberWithString:results[@"foo"]]. Aber was mich interessiert ist, wie die NSJSONSerialization-Klasse JSON-Nummern deserialisiert, damit ich weiß, ob das wirklich notwendig ist oder nicht.

Antwort

2

Um die Frage im Titel zu beantworten: Nein, es erzeugt keine NSNumber Objekte. Sie können dies leicht testen:

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]); 

wird __NSCFNumber drucken.

können Sie das NSNumber Objekt zu einem NSDecimalNumber mit [NSDecimalNumber decimalNumberWithDecimal:[number decimalValue]] wandeln, sondern nach der docs für decimalValue

zurück Der Wert nicht exakt für Schwimmer sein, ist garantiert und doppelte Werte.

3

Die kurze Antwort ist, dass Sie nicht zu JSON serialisieren sollten, wenn Sie NSDecimalNumber Genauigkeitsgrade benötigen. JSON hat nur ein Zahlenformat: double mit einer geringeren Genauigkeit als NSDecimalNumber.

Die lange Antwort, die nur von akademischem Interesse ist, weil die kurze Antwort auch die richtige Antwort ist, ist "nicht unbedingt". NSJSONSerialization deserialisiert manchmal als NSDecimalNumber, aber es ist nicht dokumentiert, und ich habe nicht bestimmt, unter welchen Umständen dies der Fall ist. Zum Beispiel:

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 =ull; 

    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]; 

und im Debugger:

(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 

So davon machen, was du willst.Sie sollten definitiv nicht davon ausgehen, dass Sie eine NSDecimalNumber zurück erhalten, wenn Sie eine NSDecimalNumber zu JSON serialisieren.

Aber wiederum sollten Sie NSDecimalNumbers nicht in JSON speichern.

+5

Dies ist falsch. Das JSON-Format interessiert sich nicht für Float oder Double Precision. Es ist der JSON-Parser, der die Präzision verliert. – Sulthan

+0

@icodestuff: Wenn ich Ihren Code mit 'double dl = 8.92918e-128;' verwende, ist 'dict_back'' nil'. Haben Sie eine Idee, ob es eine Möglichkeit gibt, JSON-Deserialisierung für solche Werte mit 'NSJSONSerialization' oder anderen Bibliotheken zu verwenden? – sergio

+0

Wie Sulthan sagte, kümmert sich JSON nicht um Präzision. Wenn Apples API den Spezifikationen entspricht, sollte NSDecimalNumber verwendet werden. – xtravar

4

NSJSONSerialization (und JSONSerialization in Swift) folgen den allgemeinen Muster:

  1. Wenn eine Zahl nur einen ganzzahligen Teil hat (kein Dezimal- oder Exponenten), versucht es als long long zu parsen. Wenn das nicht überläuft, geben Sie NSNumber mit long long zurück.
  2. Versuch, ein Doppel mit strtod_l zu analysieren. Wenn es nicht überläuft, geben Sie NSNumber mit double zurück.
  3. In allen anderen Fällen versuchen Sie, NSDecimalNumber verwenden, die einen viel größeren Bereich von Werten unterstützt, insbesondere eine Mantisse bis zu 38 Ziffern und einen Exponenten zwischen -128 ... 127.

Wenn man sich andere Beispiele Leute geschrieben haben, können Sie sehen, dass, wenn der Wert den Bereich oder Genauigkeit eines double überschreitet man eine NSDecimalNumber zurück.

1

Ich hatte das gleiche Problem, außer dass ich Swift 3 verwende. Ich machte a patched version of the JSONSerialization class, die alle Zahlen als Decimal 's analysiert. Es kann JSON nur analysieren/deserialisieren, hat jedoch keinen Serialisierungscode. Es basiert auf Apples Open-Source-Neuimplementierung von Foundation in Swift.

Verwandte Themen