webentwicklung-frage-antwort-db.com.de

Die richtige Methode zum Instanziieren einer NSDecimalNumber aus Float oder Double

Ich bin auf der Suche nach der besten Möglichkeit, eine NSDecimalNumber von einem Double oder Short zu instanziieren. Es gibt folgende NSNumber-Klassen- und Instanzmethoden ...

+NSNumber numberWithFloat
+NSNumber numberWithDouble
-NSNumber initWithFloat
-NSNumber initWithDouble

aber diese scheinen NSNumber zurückzugeben. Auf der anderen Seite definiert NSDecimalNumber Folgendes:

+NSDecimalNumber decimalNumberWithMantissa:exponent:isNegative:
+NSDecimalNumber decimalNumberWithDecimal:

Hier gibt es einige Möglichkeiten, welchen Weg Sie gehen sollen. Xcode generiert eine Warnung, wenn für NSDecimalNumber der Rückgabewert der oben genannten Komfortmethode NSNumber festgelegt ist.

Würde mich über den saubersten und korrekten Weg freuen ...

37
Paul

Sie waren auf dem richtigen Weg (mit Einschränkungen, siehe unten). Sie sollten nur die Initialisierer von NSNumber verwenden können, da diese von NSDecimalNumber geerbt werden.

NSDecimalNumber *floatDecimal = [[[NSDecimalNumber alloc] initWithFloat:42.13f] autorelease];
NSDecimalNumber *doubleDecimal = [[[NSDecimalNumber alloc] initWithDouble:53.1234] autorelease];
NSDecimalNumber *intDecimal = [[[NSDecimalNumber alloc] initWithInt:53] autorelease];

NSLog(@"floatDecimal floatValue=%6.3f", [floatDecimal floatValue]);
NSLog(@"doubleDecimal doubleValue=%6.3f", [doubleDecimal doubleValue]); 
NSLog(@"intDecimal intValue=%d", [intDecimal intValue]);

Weitere Informationen zu diesem Thema finden Sie hier .

Ich habe jedoch viele Diskussionen über Stack Overflow und das Internet über Probleme bei der Initialisierung von NSDecimalNumber gesehen. Es scheint einige Probleme zu geben, die sich auf die Genauigkeit und die Konvertierung in/aus Double mit NSDecimalNumber beziehen. Insbesondere, wenn Sie die von NSNumber geerbten Initialisierungsmitglieder verwenden.

Ich habe den folgenden Test beendet:

double dbl = 36.76662445068359375000;
id xx1 = [NSDecimalNumber numberWithDouble: dbl]; // Don't do this
id xx2 = [[[NSDecimalNumber alloc] initWithDouble: dbl] autorelease];
id xx3 = [NSDecimalNumber decimalNumberWithString:@"36.76662445068359375000"];
id xx4 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L exponent:-17 isNegative:NO];

NSLog(@"raw doubleValue: %.20f,              %.17f", dbl, dbl);

NSLog(@"xx1 doubleValue: %.20f, description: %@", [xx1 doubleValue], xx1);   
NSLog(@"xx2 doubleValue: %.20f, description: %@", [xx2 doubleValue], xx2);   
NSLog(@"xx3 doubleValue: %.20f, description: %@", [xx3 doubleValue], xx3);   
NSLog(@"xx4 doubleValue: %.20f, description: %@", [xx4 doubleValue], xx4);   

Die Ausgabe ist:

raw doubleValue: 36.76662445068359375000               36.76662445068359375
xx1 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
xx2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168
xx3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375
xx4 doubleValue: 36.76662445068357953915, description: 36.76662445068359375

Sie können also feststellen, dass bei Verwendung der numberWithDouble - Komfortmethode für NSNumber (die Sie nicht wirklich verwenden sollten, da sie den falschen Zeigertyp zurückgibt) und sogar für den Initialisierer initWithDouble (diesen IMO) "sollte" zum Aufrufen in Ordnung sein) Sie erhalten eine NSDecimalNumber mit einer internen Darstellung (wie durch Aufrufen von description für das Objekt zurückgegeben), die nicht so genau ist wie die, die Sie beim Aufrufen von decimalNumberWithString: oder decimalNumberWithMantissa:exponent:isNegative: erhalten.

Beachten Sie außerdem, dass das Zurückkonvertieren in ein Double durch Aufrufen von doubleValue in der NSDecimalNumber -Instanz an Genauigkeit verliert, aber interessanterweise unabhängig vom aufgerufenen Initialisierer identisch ist.

Ich halte es daher für empfehlenswert, eine der decimalNumberWith*-Methoden zu verwenden, die auf der Klassenebene NSDecimalNumber deklariert wurden, um Ihre NSDecimalNumber-Instanzen bei hochgenauen Gleitkommazahlen zu erstellen.

Wie können Sie diese Initialisierer einfach aufrufen, wenn Sie eine double, float oder andere NSNumber haben?

Zwei Methoden beschrieben hier "Arbeit", aber immer noch Präzisionsprobleme.

Wenn Sie die Nummer bereits als NSNumber gespeichert haben, sollte dies funktionieren:

id n = [NSNumber numberWithDouble:dbl];
id dn1 = [NSDecimalNumber decimalNumberWithDecimal:[n decimalValue]];

NSLog(@"  n doubleValue: %.20f, description: %@", [n doubleValue], n);   
NSLog(@"dn1 doubleValue: %.20f, description: %@", [dn1 doubleValue], dn1);  

Wie Sie der Ausgabe unten entnehmen können, werden einige der weniger signifikanten Ziffern entfernt:

  n doubleValue: 36.76662445068359375000, description: 36.76662445068359
dn1 doubleValue: 36.76662445068357953915, description: 36.76662445068359

Wenn die Zahl ein Primitiv ist (float oder double), sollte dies funktionieren :

id dn2 = [NSDecimalNumber decimalNumberWithMantissa:(dbl * pow(10, 17))
                                          exponent:-17 
                                        isNegative:(dbl < 0 ? YES : NO)];

NSLog(@"dn2 doubleValue: %.20f, description: %@", [dn2 doubleValue], dn2);

Aber Sie werden wieder Präzisionsfehler bekommen. Wie Sie in der Ausgabe unten sehen können:

dn2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168

Der Grund, den ich für diesen Präzisionsverlust halte, liegt in der Einbeziehung der Gleitkommamultiplikation, da der folgende Code einwandfrei funktioniert:

id dn3 = [NSDecimalNumber decimalNumberWithMantissa:3676662445068359375L 
                                           exponent:-17 
                                         isNegative:NO];

NSLog(@"dn3 doubleValue: %.20f, description: %@", [dn3 doubleValue], dn3);   

Ausgabe:

dn3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375

Die beständigste Konvertierung/Initialisierung von double oder float nach NSDecimalNumber ist daher die Verwendung von decimalNumberWithString:. Aber, wie Brad Larson in seiner Antwort betont hat, könnte dies etwas langsam sein. Seine Technik zur Konvertierung mit NSScanner ist möglicherweise besser, wenn die Leistung ein Problem darstellt.

70
orj

Es gibt einige Probleme, die auftreten können, wenn Sie beim Erstellen einer NSDecimalNumber die Initialisierer von NSNumber verwenden. Siehe das Beispiel in diese Frage für einen großartigen Fall. Ich vertraue auch Bill Bumgarners Wort in diesem Zusammenhang von seine Antwort in der Kakao-Entwickler-Mailingliste . Siehe auch Ashleys Antwort hier .

Da ich bezüglich dieser Konvertierungsprobleme paranoid bin, habe ich in der Vergangenheit folgenden Code zum Erstellen von NSDecimal-Strukturen verwendet:

NSDecimal decimalValueForDouble(double doubleValue)
{
    NSDecimal result;
    NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue];
    NSScanner *theScanner = [[NSScanner alloc] initWithString:stringRepresentationOfDouble];
    [stringRepresentationOfDouble release];

    [theScanner scanDecimal:&result];
    [theScanner release];

    return result;
}

Der Grund für den komplexen Code hier ist, dass ich gefunden hat, dass NSScanner etwa 90% schneller ist als eine NSDecimalNumber aus einem String initialisiert. Ich verwende NSDecimal-Strukturen, weil sie gegenüber ihren NSDecimalNumber-Brüdern signifikante Leistungsvorteile erzielen können.

Für einen einfacheren, aber etwas langsameren NSDecimalNumber-Ansatz könnten Sie etwas verwenden 

NSDecimalNumber *decimalNumberForDouble(double doubleValue)
{
    NSString *stringRepresentationOfDouble = [[NSString alloc] initWithFormat:@"%f", doubleValue];
    NSDecimalNumber *stringProcessor = [[NSDecimalNumber alloc] initWithString:stringRepresentationOfDouble];
    [stringRepresentationOfDouble release];
    return [stringProcessor autorelease];
}

Wenn Sie jedoch mit NSDecimalNumbers arbeiten, sollten Sie alles tun, um zu vermeiden, dass ein Wert an einem beliebigen Punkt in eine Fließkommazahl umgewandelt wird. Lesen Sie die Zeichenfolgenwerte aus Ihren Textfeldern zurück und konvertieren Sie sie direkt in NSDecimalNumbers. Berechnen Sie sie mit NSDecimalNumbers und schreiben Sie sie als NSStrings oder Dezimalwerte in Core Data auf die Festplatte. Sie beginnen mit der Einführung von Fließkommadarstellungsfehlern, sobald Ihre Werte an einem beliebigen Punkt in Fließkommazahlen umgewandelt werden.

15
Brad Larson

Du könntest es versuchen:

float myFloat = 42.0;
NSDecimal myFloatDecimal = [[NSNumber numberWithFloat:myFloat] decimalValue];
NSDecimalNumber* num = [NSDecimalNumber decimalNumberWithDecimal:myFloatDecimal];

Anscheinend gibt es schnellere Wege, dies zu tun , aber es lohnt sich vielleicht nicht, wenn der Code keinen Engpass darstellt.

1
fbrereto

-[NSNumber initWithFloat:] und -[NSNumber initWithDouble:] geben eigentlich keine NSNumber *-Objekte zurück, sie geben id zurück und NSDecimalNumber erbt von NSNumber. Sie können also Folgendes verwenden:

NSDecimalNumber *decimal = [[[NSDecimalNumber alloc] initWithFloat:1.1618] autorelease];

Und duh, ich habe gerade realisiert, dass das gleiche für +numberWithFloat: gilt, also kannst du das stattdessen verwenden.

0
Seth Kingsley