39 votes

Manière correcte d'instancier un NSDecimalNumber à partir de float ou double

Je suis à la recherche de la meilleure façon d'instancier un NSDecimalNumber à partir d'un lit double ou court. Il y a les suivantes NSNumber de classe et les méthodes d'instance...

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

mais ceux-ci semblent de retour NSNumber. De l'autre côté, NSDecimalNumber définit les éléments suivants:

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

Il y a quelques possibilités ici sur la voie à suivre. Xcode génère un message d'avertissement si vous avez un NSDecimalNumber réglé à la valeur de retour de la NSNumber commodité des méthodes ci-dessus.

Apprécierait d'entrée sur la plus propre et la manière correcte de s'en aller...

72voto

orj Points 4480

Vous étiez sur la bonne voie (avec des réserves, voir ci-dessous). Vous devriez être en mesure d'utiliser seulement la initialisers de NSNumber comme ils sont hérités par NSDecimalNumber.

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

Plus d'info sur ce sujet peuvent être trouvées ici.

Cependant, j'ai vu beaucoup de discussions sur un Débordement de Pile et sur le web à propos de problèmes avec l'initialisation NSDecimalNumber. Il semble y avoir certaines questions relatives à la précision et à la conversion/de chambres doubles avec NSDecimalNumber. En particulier lorsque vous utilisez l'initialisation des membres hérité de NSNumber.

J'ai frappé le test ci-dessous:

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

La sortie est:

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

Donc vous pouvez voir que lors de l'utilisation de l' numberWithDouble méthode de convenance sur NSNumber (que vous ne devriez pas vraiment à utiliser grâce à lui retourner le mauvais type de pointeur) et même de l'initialiser initWithDouble (que l'OMI "devrait" être OK pour appeler), vous obtenez un NSDecimalNumber avec une représentation interne (retourné par l'appel de description sur l'objet) ce n'est pas aussi précis que celui que vous obtenez lorsque vous appelez decimalNumberWithString: ou decimalNumberWithMantissa:exponent:isNegative:.

Notez également que de le convertir en un lit double en appelant doubleValue sur le NSDecimalNumber de l'instance est en train de perdre de la précision, mais, fait intéressant, le même quelle que soit la initialiser vous appeler.

Donc au final, je pense que c'est vous recommandons d'utiliser l'un de l' decimalNumberWith* méthodes déclarées à l' NSDecimalNumber le niveau de la classe pour créer votre NSDecimalNumber cas lorsque l'on traite avec une grande précision des nombres à virgule flottante.

Alors, comment appelez-vous ces initialisers facilement quand vous avez un double, float ou d'autres NSNumber?

Deux méthodes décrites ici "travailler", mais encore la précision des questions.

Si vous avez déjà le nombre stocké comme un NSNumber alors cela devrait fonctionner:

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

Mais comme vous pouvez le voir à partir de la sortie ci-dessous il lops une partie de la moins de chiffres significatifs:

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

Si le nombre est une primitive (float ou double), cela devrait fonctionner:

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

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

Mais, vous obtiendrez des erreurs de précision de nouveau. Comme vous pouvez le voir dans la sortie ci-dessous:

dn2 doubleValue: 36.76662445068357953915, description: 36.76662445068359168

La raison pour laquelle je pense que pour cette précision perte est due à la participation de la virgule flottante se multiplient, parce que le code suivant fonctionne:

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

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

Sortie:

dn3 doubleValue: 36.76662445068357953915, description: 36.76662445068359375

Donc, le plus systématiquement la précision de la conversion/initialisation à partir d'un double ou float de NSDecimalNumber est à l'aide de decimalNumberWithString:. Mais, comme Brad Larson a souligné, dans sa réponse, cela pourrait être un peu lent. Sa technique pour la conversion en utilisant NSScanner peut-être mieux si la performance est un problème.

15voto

Brad Larson Points 122629

Il ya quelques problèmes qui peuvent survenir si vous utilisez NSNumber de initialiseurs lors de la création d'un NSDecimalNumber. Voir l'exemple posté dans cette question pour un cas très de la. Aussi, j'ai confiance dans le projet de Loi Bumgarner la parole de sur cette de son intervention dans la cacao-dev mailing list. Voir aussi Ashley réponse ici.

Parce que je suis paranoïaque à propos de ces problèmes de conversion, j'ai utilisé le code suivant dans le passé pour créer NSDecimal les structures:

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

La raison pour laquelle le code complexe ici, c'est que j'ai trouvé NSScanner à environ 90% plus rapide que l'initialisation d'un NSDecimalNumber à partir d'une chaîne. J'utilise NSDecimal structs, car ils peuvent apporter d'importants avantages en termes de performances au cours de leur NSDecimalNumber frères.

Pour une plus simple mais un peu plus lent NSDecimalNumber approche, vous pouvez utiliser quelque chose comme

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

Toutefois, si vous travaillez avec NSDecimalNumbers, vous aurez envie de faire ce que vous pouvez pour éviter d'avoir une valeur devenue en fonte pour un nombre à virgule flottante à tout moment. Lire les valeurs de chaîne à partir de vos champs de texte et de les convertir directement à NSDecimalNumbers, faites vos comptes avec NSDecimalNumbers, et de les écrire sur le disque comme NSStrings ou des valeurs décimales en Base de Données. Vous commencer à introduire représentation à virgule flottante les erreurs dès que vos valeurs sont converties en nombres à virgule flottante à tout moment.

1voto

fbrereto Points 21711

Tu pourrais essayer:

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

Apparemment, il existe des moyens plus rapides pour le faire , mais cela ne vaut peut-être pas la peine si le code n'est pas un goulot d'étranglement.

0voto

Seth Kingsley Points 182

-[NSNumber initWithFloat:] et -[NSNumber initWithDouble:] ne retournent pas les objets NSNumber * , ils renvoient id et NSDecimalNumber héritent de NSNumber , vous pouvez donc utiliser:

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

Et puis, je viens de me rendre compte que la même chose vaut pour +numberWithFloat: , donc vous pouvez simplement l'utiliser à la place.

-2voto

hoha Points 3626

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X